Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

selftests/landlock: Add PID tests for audit records

Add audit.thread tests to check that the PID tied to a domain is not a
thread ID but the thread group ID. These new tests would not pass
without the previous TGID fix.

Extend matches_log_domain_allocated() to check against the PID that
created the domain.

Test coverage for security/landlock is 93.6% of 1524 lines according to
gcc/gcov-14.

Cc: Christian Brauner <brauner@kernel.org>
Cc: Günther Noack <gnoack@google.com>
Cc: Paul Moore <paul@paul-moore.com>
Link: https://lore.kernel.org/r/20250410171725.1265860-3-mic@digikod.net
Signed-off-by: Mickaël Salaün <mic@digikod.net>

+141 -10
+14 -7
tools/testing/selftests/landlock/audit.h
··· 300 300 return err; 301 301 } 302 302 303 - static int __maybe_unused matches_log_domain_allocated(int audit_fd, 303 + static int __maybe_unused matches_log_domain_allocated(int audit_fd, pid_t pid, 304 304 __u64 *domain_id) 305 305 { 306 - return audit_match_record( 307 - audit_fd, AUDIT_LANDLOCK_DOMAIN, 308 - REGEX_LANDLOCK_PREFIX 309 - " status=allocated mode=enforcing pid=[0-9]\\+ uid=[0-9]\\+" 310 - " exe=\"[^\"]\\+\" comm=\".*_test\"$", 311 - domain_id); 306 + static const char log_template[] = REGEX_LANDLOCK_PREFIX 307 + " status=allocated mode=enforcing pid=%d uid=[0-9]\\+" 308 + " exe=\"[^\"]\\+\" comm=\".*_test\"$"; 309 + char log_match[sizeof(log_template) + 10]; 310 + int log_match_len; 311 + 312 + log_match_len = 313 + snprintf(log_match, sizeof(log_match), log_template, pid); 314 + if (log_match_len > sizeof(log_match)) 315 + return -E2BIG; 316 + 317 + return audit_match_record(audit_fd, AUDIT_LANDLOCK_DOMAIN, log_match, 318 + domain_id); 312 319 } 313 320 314 321 static int __maybe_unused matches_log_domain_deallocated(
+125 -2
tools/testing/selftests/landlock/audit_test.c
··· 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> ··· 105 104 matches_log_signal(_metadata, self->audit_fd, 106 105 getppid(), &denial_dom)); 107 106 EXPECT_EQ(0, matches_log_domain_allocated( 108 - self->audit_fd, &allocated_dom)); 107 + self->audit_fd, getpid(), 108 + &allocated_dom)); 109 109 EXPECT_NE(denial_dom, 1); 110 110 EXPECT_NE(denial_dom, 0); 111 111 EXPECT_EQ(denial_dom, allocated_dom); ··· 156 154 EXPECT_EQ(0, setsockopt(self->audit_fd, SOL_SOCKET, SO_RCVTIMEO, 157 155 &audit_tv_default, sizeof(audit_tv_default))); 158 156 EXPECT_EQ(0, close(ruleset_fd)); 157 + } 158 + 159 + struct thread_data { 160 + pid_t parent_pid; 161 + int ruleset_fd, pipe_child, pipe_parent; 162 + }; 163 + 164 + static void *thread_audit_test(void *arg) 165 + { 166 + const struct thread_data *data = (struct thread_data *)arg; 167 + uintptr_t err = 0; 168 + char buffer; 169 + 170 + /* TGID and TID are different for a second thread. */ 171 + if (getpid() == gettid()) { 172 + err = 1; 173 + goto out; 174 + } 175 + 176 + if (landlock_restrict_self(data->ruleset_fd, 0)) { 177 + err = 2; 178 + goto out; 179 + } 180 + 181 + if (close(data->ruleset_fd)) { 182 + err = 3; 183 + goto out; 184 + } 185 + 186 + /* Creates a denial to get the domain ID. */ 187 + if (kill(data->parent_pid, 0) != -1) { 188 + err = 4; 189 + goto out; 190 + } 191 + 192 + if (EPERM != errno) { 193 + err = 5; 194 + goto out; 195 + } 196 + 197 + /* Signals the parent to read denial logs. */ 198 + if (write(data->pipe_child, ".", 1) != 1) { 199 + err = 6; 200 + goto out; 201 + } 202 + 203 + /* Waits for the parent to update audit filters. */ 204 + if (read(data->pipe_parent, &buffer, 1) != 1) { 205 + err = 7; 206 + goto out; 207 + } 208 + 209 + out: 210 + close(data->pipe_child); 211 + close(data->pipe_parent); 212 + return (void *)err; 213 + } 214 + 215 + /* Checks that the PID tied to a domain is not a TID but the TGID. */ 216 + TEST_F(audit, thread) 217 + { 218 + const struct landlock_ruleset_attr ruleset_attr = { 219 + .scoped = LANDLOCK_SCOPE_SIGNAL, 220 + }; 221 + __u64 denial_dom = 1; 222 + __u64 allocated_dom = 2; 223 + __u64 deallocated_dom = 3; 224 + pthread_t thread; 225 + int pipe_child[2], pipe_parent[2]; 226 + char buffer; 227 + struct thread_data child_data; 228 + 229 + child_data.parent_pid = getppid(); 230 + ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC)); 231 + child_data.pipe_child = pipe_child[1]; 232 + ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC)); 233 + child_data.pipe_parent = pipe_parent[0]; 234 + child_data.ruleset_fd = 235 + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); 236 + ASSERT_LE(0, child_data.ruleset_fd); 237 + 238 + /* TGID and TID are the same for the initial thread . */ 239 + EXPECT_EQ(getpid(), gettid()); 240 + EXPECT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); 241 + ASSERT_EQ(0, pthread_create(&thread, NULL, thread_audit_test, 242 + &child_data)); 243 + 244 + /* Waits for the child to generate a denial. */ 245 + ASSERT_EQ(1, read(pipe_child[0], &buffer, 1)); 246 + EXPECT_EQ(0, close(pipe_child[0])); 247 + 248 + /* Matches the signal log to get the domain ID. */ 249 + EXPECT_EQ(0, matches_log_signal(_metadata, self->audit_fd, 250 + child_data.parent_pid, &denial_dom)); 251 + EXPECT_NE(denial_dom, 1); 252 + EXPECT_NE(denial_dom, 0); 253 + 254 + EXPECT_EQ(0, matches_log_domain_allocated(self->audit_fd, getpid(), 255 + &allocated_dom)); 256 + EXPECT_EQ(denial_dom, allocated_dom); 257 + 258 + /* Updates filter rules to match the drop record. */ 259 + set_cap(_metadata, CAP_AUDIT_CONTROL); 260 + EXPECT_EQ(0, audit_filter_drop(self->audit_fd, AUDIT_ADD_RULE)); 261 + EXPECT_EQ(0, audit_filter_exe(self->audit_fd, &self->audit_filter, 262 + AUDIT_DEL_RULE)); 263 + clear_cap(_metadata, CAP_AUDIT_CONTROL); 264 + 265 + /* Signals the thread to exit, which will generate a domain deallocation. */ 266 + ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); 267 + EXPECT_EQ(0, close(pipe_parent[1])); 268 + ASSERT_EQ(0, pthread_join(thread, NULL)); 269 + 270 + EXPECT_EQ(0, setsockopt(self->audit_fd, SOL_SOCKET, SO_RCVTIMEO, 271 + &audit_tv_dom_drop, sizeof(audit_tv_dom_drop))); 272 + EXPECT_EQ(0, matches_log_domain_deallocated(self->audit_fd, 1, 273 + &deallocated_dom)); 274 + EXPECT_EQ(denial_dom, deallocated_dom); 275 + EXPECT_EQ(0, setsockopt(self->audit_fd, SOL_SOCKET, SO_RCVTIMEO, 276 + &audit_tv_default, sizeof(audit_tv_default))); 159 277 } 160 278 161 279 FIXTURE(audit_flags) ··· 392 270 393 271 /* Checks domain information records. */ 394 272 EXPECT_EQ(0, matches_log_domain_allocated( 395 - self->audit_fd, &allocated_dom)); 273 + self->audit_fd, getpid(), 274 + &allocated_dom)); 396 275 EXPECT_NE(*self->domain_id, 1); 397 276 EXPECT_NE(*self->domain_id, 0); 398 277 EXPECT_EQ(*self->domain_id, allocated_dom);
+2 -1
tools/testing/selftests/landlock/fs_test.c
··· 5964 5964 EXPECT_EQ(EXDEV, errno); 5965 5965 EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.refer", 5966 5966 dir_s1d1)); 5967 - EXPECT_EQ(0, matches_log_domain_allocated(self->audit_fd, NULL)); 5967 + EXPECT_EQ(0, 5968 + matches_log_domain_allocated(self->audit_fd, getpid(), NULL)); 5968 5969 EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.refer", 5969 5970 dir_s1d3)); 5970 5971