1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Tests for the state checks in AF_UNIX listen().
4 *
5 * The central case is a regression test: listen() on a bound socket that
6 * is already connected (i.e. not in TCP_CLOSE or TCP_LISTEN state) must
7 * fail with EINVAL. A prior change accidentally let it return success
8 * without doing anything, because a helper called in between reset the
9 * error code to 0. The neighbouring checks (unbound, already listening)
10 * are tested too so they cannot silently regress the same way.
11 *
12 * Every case runs for both listenable socket types (SOCK_STREAM and
13 * SOCK_SEQPACKET) and both pathname and abstract addresses.
14 */
15 #define _GNU_SOURCE
16
17 #include <errno.h>
18 #include <stddef.h>
19 #include <stdio.h>
20 #include <string.h>
21 #include <unistd.h>
22
23 #include <sys/socket.h>
24 #include <sys/un.h>
25
26 #include "kselftest_harness.h"
27
28 #define SK_NAME "unix_listen_sk"
29 #define SRV_NAME "unix_listen_srv"
30
FIXTURE(unix_listen)31 FIXTURE(unix_listen)
32 {
33 int sk; /* socket under test */
34 int server; /* a listening peer, when a test needs one */
35 struct sockaddr_un addr, srv_addr;
36 socklen_t addrlen, srv_addrlen;
37 };
38
FIXTURE_VARIANT(unix_listen)39 FIXTURE_VARIANT(unix_listen)
40 {
41 int type;
42 int abstract;
43 };
44
FIXTURE_VARIANT_ADD(unix_listen,stream_pathname)45 FIXTURE_VARIANT_ADD(unix_listen, stream_pathname)
46 {
47 .type = SOCK_STREAM,
48 .abstract = 0,
49 };
50
FIXTURE_VARIANT_ADD(unix_listen,stream_abstract)51 FIXTURE_VARIANT_ADD(unix_listen, stream_abstract)
52 {
53 .type = SOCK_STREAM,
54 .abstract = 1,
55 };
56
FIXTURE_VARIANT_ADD(unix_listen,seqpacket_pathname)57 FIXTURE_VARIANT_ADD(unix_listen, seqpacket_pathname)
58 {
59 .type = SOCK_SEQPACKET,
60 .abstract = 0,
61 };
62
FIXTURE_VARIANT_ADD(unix_listen,seqpacket_abstract)63 FIXTURE_VARIANT_ADD(unix_listen, seqpacket_abstract)
64 {
65 .type = SOCK_SEQPACKET,
66 .abstract = 1,
67 };
68
69 /* Fill @addr with a pathname or abstract address named @name. */
unix_set_addr(struct sockaddr_un * addr,const char * name,int abstract)70 static socklen_t unix_set_addr(struct sockaddr_un *addr, const char *name,
71 int abstract)
72 {
73 size_t len = strlen(name);
74
75 memset(addr, 0, sizeof(*addr));
76 addr->sun_family = AF_UNIX;
77 /* An abstract address leads with a NUL and has no filesystem entry. */
78 memcpy(addr->sun_path + (abstract ? 1 : 0), name, len);
79
80 return offsetof(struct sockaddr_un, sun_path) + len + 1;
81 }
82
FIXTURE_SETUP(unix_listen)83 FIXTURE_SETUP(unix_listen)
84 {
85 self->sk = -1;
86 self->server = -1;
87 self->addrlen = unix_set_addr(&self->addr, SK_NAME, variant->abstract);
88 self->srv_addrlen = unix_set_addr(&self->srv_addr, SRV_NAME,
89 variant->abstract);
90 }
91
FIXTURE_TEARDOWN(unix_listen)92 FIXTURE_TEARDOWN(unix_listen)
93 {
94 if (self->sk >= 0)
95 close(self->sk);
96 if (self->server >= 0)
97 close(self->server);
98
99 /* Pathname sockets leave a filesystem entry behind; abstract ones do not. */
100 if (!variant->abstract) {
101 remove(SK_NAME);
102 remove(SRV_NAME);
103 }
104 }
105
106 /* A bound socket in TCP_CLOSE is the normal, allowed case. */
TEST_F(unix_listen,bound_is_ok)107 TEST_F(unix_listen, bound_is_ok)
108 {
109 int err;
110
111 self->sk = socket(AF_UNIX, variant->type, 0);
112 ASSERT_LE(0, self->sk);
113
114 err = bind(self->sk, (struct sockaddr *)&self->addr, self->addrlen);
115 ASSERT_EQ(0, err);
116
117 err = listen(self->sk, 8);
118 EXPECT_EQ(0, err);
119 }
120
121 /* Listening again on an already-listening socket (TCP_LISTEN) is allowed. */
TEST_F(unix_listen,relisten_is_ok)122 TEST_F(unix_listen, relisten_is_ok)
123 {
124 int err;
125
126 self->sk = socket(AF_UNIX, variant->type, 0);
127 ASSERT_LE(0, self->sk);
128
129 err = bind(self->sk, (struct sockaddr *)&self->addr, self->addrlen);
130 ASSERT_EQ(0, err);
131
132 err = listen(self->sk, 8);
133 ASSERT_EQ(0, err);
134
135 err = listen(self->sk, 16);
136 EXPECT_EQ(0, err);
137 }
138
139 /* listen() on an unbound socket fails: there is nothing to listen on. */
TEST_F(unix_listen,unbound_is_einval)140 TEST_F(unix_listen, unbound_is_einval)
141 {
142 int err;
143
144 self->sk = socket(AF_UNIX, variant->type, 0);
145 ASSERT_LE(0, self->sk);
146
147 err = listen(self->sk, 8);
148 EXPECT_EQ(-1, err);
149 EXPECT_EQ(EINVAL, errno);
150 }
151
152 /*
153 * The regression: a bound socket that has already been connected is not in
154 * TCP_CLOSE or TCP_LISTEN, so listen() must reject it with EINVAL rather
155 * than quietly succeeding.
156 */
TEST_F(unix_listen,connected_is_einval)157 TEST_F(unix_listen, connected_is_einval)
158 {
159 int err;
160
161 self->server = socket(AF_UNIX, variant->type, 0);
162 ASSERT_LE(0, self->server);
163
164 err = bind(self->server, (struct sockaddr *)&self->srv_addr,
165 self->srv_addrlen);
166 ASSERT_EQ(0, err);
167
168 err = listen(self->server, 8);
169 ASSERT_EQ(0, err);
170
171 self->sk = socket(AF_UNIX, variant->type, 0);
172 ASSERT_LE(0, self->sk);
173
174 /* Bind first so the unbound check does not mask the state check. */
175 err = bind(self->sk, (struct sockaddr *)&self->addr, self->addrlen);
176 ASSERT_EQ(0, err);
177
178 err = connect(self->sk, (struct sockaddr *)&self->srv_addr,
179 self->srv_addrlen);
180 ASSERT_EQ(0, err);
181
182 err = listen(self->sk, 8);
183 EXPECT_EQ(-1, err);
184 EXPECT_EQ(EINVAL, errno);
185 }
186
187 TEST_HARNESS_MAIN
188