|
9 | 9 | #include <errno.h>
|
10 | 10 | #include <limits.h>
|
11 | 11 | #include <linux/landlock.h>
|
| 12 | +#include <pthread.h> |
12 | 13 | #include <stdlib.h>
|
13 | 14 | #include <sys/mount.h>
|
14 | 15 | #include <sys/prctl.h>
|
@@ -104,7 +105,8 @@ TEST_F(audit, layers)
|
104 | 105 | matches_log_signal(_metadata, self->audit_fd,
|
105 | 106 | getppid(), &denial_dom));
|
106 | 107 | EXPECT_EQ(0, matches_log_domain_allocated(
|
107 |
| - self->audit_fd, &allocated_dom)); |
| 108 | + self->audit_fd, getpid(), |
| 109 | + &allocated_dom)); |
108 | 110 | EXPECT_NE(denial_dom, 1);
|
109 | 111 | EXPECT_NE(denial_dom, 0);
|
110 | 112 | EXPECT_EQ(denial_dom, allocated_dom);
|
@@ -156,6 +158,126 @@ TEST_F(audit, layers)
|
156 | 158 | EXPECT_EQ(0, close(ruleset_fd));
|
157 | 159 | }
|
158 | 160 |
|
| 161 | +struct thread_data { |
| 162 | + pid_t parent_pid; |
| 163 | + int ruleset_fd, pipe_child, pipe_parent; |
| 164 | +}; |
| 165 | + |
| 166 | +static void *thread_audit_test(void *arg) |
| 167 | +{ |
| 168 | + const struct thread_data *data = (struct thread_data *)arg; |
| 169 | + uintptr_t err = 0; |
| 170 | + char buffer; |
| 171 | + |
| 172 | + /* TGID and TID are different for a second thread. */ |
| 173 | + if (getpid() == gettid()) { |
| 174 | + err = 1; |
| 175 | + goto out; |
| 176 | + } |
| 177 | + |
| 178 | + if (landlock_restrict_self(data->ruleset_fd, 0)) { |
| 179 | + err = 2; |
| 180 | + goto out; |
| 181 | + } |
| 182 | + |
| 183 | + if (close(data->ruleset_fd)) { |
| 184 | + err = 3; |
| 185 | + goto out; |
| 186 | + } |
| 187 | + |
| 188 | + /* Creates a denial to get the domain ID. */ |
| 189 | + if (kill(data->parent_pid, 0) != -1) { |
| 190 | + err = 4; |
| 191 | + goto out; |
| 192 | + } |
| 193 | + |
| 194 | + if (EPERM != errno) { |
| 195 | + err = 5; |
| 196 | + goto out; |
| 197 | + } |
| 198 | + |
| 199 | + /* Signals the parent to read denial logs. */ |
| 200 | + if (write(data->pipe_child, ".", 1) != 1) { |
| 201 | + err = 6; |
| 202 | + goto out; |
| 203 | + } |
| 204 | + |
| 205 | + /* Waits for the parent to update audit filters. */ |
| 206 | + if (read(data->pipe_parent, &buffer, 1) != 1) { |
| 207 | + err = 7; |
| 208 | + goto out; |
| 209 | + } |
| 210 | + |
| 211 | +out: |
| 212 | + close(data->pipe_child); |
| 213 | + close(data->pipe_parent); |
| 214 | + return (void *)err; |
| 215 | +} |
| 216 | + |
| 217 | +/* Checks that the PID tied to a domain is not a TID but the TGID. */ |
| 218 | +TEST_F(audit, thread) |
| 219 | +{ |
| 220 | + const struct landlock_ruleset_attr ruleset_attr = { |
| 221 | + .scoped = LANDLOCK_SCOPE_SIGNAL, |
| 222 | + }; |
| 223 | + __u64 denial_dom = 1; |
| 224 | + __u64 allocated_dom = 2; |
| 225 | + __u64 deallocated_dom = 3; |
| 226 | + pthread_t thread; |
| 227 | + int pipe_child[2], pipe_parent[2]; |
| 228 | + char buffer; |
| 229 | + struct thread_data child_data; |
| 230 | + |
| 231 | + child_data.parent_pid = getppid(); |
| 232 | + ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC)); |
| 233 | + child_data.pipe_child = pipe_child[1]; |
| 234 | + ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC)); |
| 235 | + child_data.pipe_parent = pipe_parent[0]; |
| 236 | + child_data.ruleset_fd = |
| 237 | + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); |
| 238 | + ASSERT_LE(0, child_data.ruleset_fd); |
| 239 | + |
| 240 | + /* TGID and TID are the same for the initial thread . */ |
| 241 | + EXPECT_EQ(getpid(), gettid()); |
| 242 | + EXPECT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); |
| 243 | + ASSERT_EQ(0, pthread_create(&thread, NULL, thread_audit_test, |
| 244 | + &child_data)); |
| 245 | + |
| 246 | + /* Waits for the child to generate a denial. */ |
| 247 | + ASSERT_EQ(1, read(pipe_child[0], &buffer, 1)); |
| 248 | + EXPECT_EQ(0, close(pipe_child[0])); |
| 249 | + |
| 250 | + /* Matches the signal log to get the domain ID. */ |
| 251 | + EXPECT_EQ(0, matches_log_signal(_metadata, self->audit_fd, |
| 252 | + child_data.parent_pid, &denial_dom)); |
| 253 | + EXPECT_NE(denial_dom, 1); |
| 254 | + EXPECT_NE(denial_dom, 0); |
| 255 | + |
| 256 | + EXPECT_EQ(0, matches_log_domain_allocated(self->audit_fd, getpid(), |
| 257 | + &allocated_dom)); |
| 258 | + EXPECT_EQ(denial_dom, allocated_dom); |
| 259 | + |
| 260 | + /* Updates filter rules to match the drop record. */ |
| 261 | + set_cap(_metadata, CAP_AUDIT_CONTROL); |
| 262 | + EXPECT_EQ(0, audit_filter_drop(self->audit_fd, AUDIT_ADD_RULE)); |
| 263 | + EXPECT_EQ(0, audit_filter_exe(self->audit_fd, &self->audit_filter, |
| 264 | + AUDIT_DEL_RULE)); |
| 265 | + clear_cap(_metadata, CAP_AUDIT_CONTROL); |
| 266 | + |
| 267 | + /* Signals the thread to exit, which will generate a domain deallocation. */ |
| 268 | + ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); |
| 269 | + EXPECT_EQ(0, close(pipe_parent[1])); |
| 270 | + ASSERT_EQ(0, pthread_join(thread, NULL)); |
| 271 | + |
| 272 | + EXPECT_EQ(0, setsockopt(self->audit_fd, SOL_SOCKET, SO_RCVTIMEO, |
| 273 | + &audit_tv_dom_drop, sizeof(audit_tv_dom_drop))); |
| 274 | + EXPECT_EQ(0, matches_log_domain_deallocated(self->audit_fd, 1, |
| 275 | + &deallocated_dom)); |
| 276 | + EXPECT_EQ(denial_dom, deallocated_dom); |
| 277 | + EXPECT_EQ(0, setsockopt(self->audit_fd, SOL_SOCKET, SO_RCVTIMEO, |
| 278 | + &audit_tv_default, sizeof(audit_tv_default))); |
| 279 | +} |
| 280 | + |
159 | 281 | FIXTURE(audit_flags)
|
160 | 282 | {
|
161 | 283 | struct audit_filter audit_filter;
|
@@ -270,7 +392,8 @@ TEST_F(audit_flags, signal)
|
270 | 392 |
|
271 | 393 | /* Checks domain information records. */
|
272 | 394 | EXPECT_EQ(0, matches_log_domain_allocated(
|
273 |
| - self->audit_fd, &allocated_dom)); |
| 395 | + self->audit_fd, getpid(), |
| 396 | + &allocated_dom)); |
274 | 397 | EXPECT_NE(*self->domain_id, 1);
|
275 | 398 | EXPECT_NE(*self->domain_id, 0);
|
276 | 399 | EXPECT_EQ(*self->domain_id, allocated_dom);
|
|
0 commit comments