xref: /aosp_15_r20/external/ltp/testcases/kernel/syscalls/setsockopt/setsockopt10.c (revision 49cdfc7efb34551c7342be41a7384b9c40d7cab7)
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