| // SPDX-License-Identifier: GPL-2.0 |
| /* Copyright (c) 2023 Meta Platforms, Inc. and affiliates. */ |
| |
| #include <stdlib.h> |
| #include <sys/types.h> |
| #include <sys/xattr.h> |
| #include <linux/fsverity.h> |
| #include <unistd.h> |
| #include <test_progs.h> |
| #include "test_get_xattr.skel.h" |
| #include "test_set_remove_xattr.skel.h" |
| #include "test_fsverity.skel.h" |
| |
| static const char testfile[] = "/tmp/test_progs_fs_kfuncs"; |
| |
| static void test_get_xattr(const char *name, const char *value, bool allow_access) |
| { |
| struct test_get_xattr *skel = NULL; |
| int fd = -1, err; |
| int v[32]; |
| |
| fd = open(testfile, O_CREAT | O_RDONLY, 0644); |
| if (!ASSERT_GE(fd, 0, "create_file")) |
| return; |
| |
| close(fd); |
| fd = -1; |
| |
| err = setxattr(testfile, name, value, strlen(value) + 1, 0); |
| if (err && errno == EOPNOTSUPP) { |
| printf("%s:SKIP:local fs doesn't support xattr (%d)\n" |
| "To run this test, make sure /tmp filesystem supports xattr.\n", |
| __func__, errno); |
| test__skip(); |
| goto out; |
| } |
| |
| if (!ASSERT_OK(err, "setxattr")) |
| goto out; |
| |
| skel = test_get_xattr__open_and_load(); |
| if (!ASSERT_OK_PTR(skel, "test_get_xattr__open_and_load")) |
| goto out; |
| |
| skel->bss->monitored_pid = getpid(); |
| err = test_get_xattr__attach(skel); |
| |
| if (!ASSERT_OK(err, "test_get_xattr__attach")) |
| goto out; |
| |
| fd = open(testfile, O_RDONLY, 0644); |
| |
| if (!ASSERT_GE(fd, 0, "open_file")) |
| goto out; |
| |
| /* Trigger security_inode_getxattr */ |
| err = getxattr(testfile, name, v, sizeof(v)); |
| |
| if (allow_access) { |
| ASSERT_EQ(err, -1, "getxattr_return"); |
| ASSERT_EQ(errno, EINVAL, "getxattr_errno"); |
| ASSERT_EQ(skel->bss->found_xattr_from_file, 1, "found_xattr_from_file"); |
| ASSERT_EQ(skel->bss->found_xattr_from_dentry, 1, "found_xattr_from_dentry"); |
| } else { |
| ASSERT_EQ(err, strlen(value) + 1, "getxattr_return"); |
| ASSERT_EQ(skel->bss->found_xattr_from_file, 0, "found_xattr_from_file"); |
| ASSERT_EQ(skel->bss->found_xattr_from_dentry, 0, "found_xattr_from_dentry"); |
| } |
| |
| out: |
| close(fd); |
| test_get_xattr__destroy(skel); |
| remove(testfile); |
| } |
| |
| /* xattr value we will set to security.bpf.foo */ |
| static const char value_foo[] = "hello"; |
| |
| static void read_and_validate_foo(struct test_set_remove_xattr *skel) |
| { |
| char value_out[32]; |
| int err; |
| |
| err = getxattr(testfile, skel->rodata->xattr_foo, value_out, sizeof(value_out)); |
| ASSERT_EQ(err, sizeof(value_foo), "getxattr size foo"); |
| ASSERT_EQ(strncmp(value_out, value_foo, sizeof(value_foo)), 0, "strncmp value_foo"); |
| } |
| |
| static void set_foo(struct test_set_remove_xattr *skel) |
| { |
| ASSERT_OK(setxattr(testfile, skel->rodata->xattr_foo, value_foo, strlen(value_foo) + 1, 0), |
| "setxattr foo"); |
| } |
| |
| static void validate_bar_match(struct test_set_remove_xattr *skel) |
| { |
| char value_out[32]; |
| int err; |
| |
| err = getxattr(testfile, skel->rodata->xattr_bar, value_out, sizeof(value_out)); |
| ASSERT_EQ(err, sizeof(skel->data->value_bar), "getxattr size bar"); |
| ASSERT_EQ(strncmp(value_out, skel->data->value_bar, sizeof(skel->data->value_bar)), 0, |
| "strncmp value_bar"); |
| } |
| |
| static void validate_bar_removed(struct test_set_remove_xattr *skel) |
| { |
| char value_out[32]; |
| int err; |
| |
| err = getxattr(testfile, skel->rodata->xattr_bar, value_out, sizeof(value_out)); |
| ASSERT_LT(err, 0, "getxattr size bar should fail"); |
| } |
| |
| static void test_set_remove_xattr(void) |
| { |
| struct test_set_remove_xattr *skel = NULL; |
| int fd = -1, err; |
| |
| fd = open(testfile, O_CREAT | O_RDONLY, 0644); |
| if (!ASSERT_GE(fd, 0, "create_file")) |
| return; |
| |
| close(fd); |
| fd = -1; |
| |
| skel = test_set_remove_xattr__open_and_load(); |
| if (!ASSERT_OK_PTR(skel, "test_set_remove_xattr__open_and_load")) |
| return; |
| |
| /* Set security.bpf.foo to "hello" */ |
| err = setxattr(testfile, skel->rodata->xattr_foo, value_foo, strlen(value_foo) + 1, 0); |
| if (err && errno == EOPNOTSUPP) { |
| printf("%s:SKIP:local fs doesn't support xattr (%d)\n" |
| "To run this test, make sure /tmp filesystem supports xattr.\n", |
| __func__, errno); |
| test__skip(); |
| goto out; |
| } |
| |
| if (!ASSERT_OK(err, "setxattr")) |
| goto out; |
| |
| skel->bss->monitored_pid = getpid(); |
| err = test_set_remove_xattr__attach(skel); |
| if (!ASSERT_OK(err, "test_set_remove_xattr__attach")) |
| goto out; |
| |
| /* First, test not _locked version of the kfuncs with getxattr. */ |
| |
| /* Read security.bpf.foo and trigger test_inode_getxattr. This |
| * bpf program will set security.bpf.bar to "world". |
| */ |
| read_and_validate_foo(skel); |
| validate_bar_match(skel); |
| |
| /* Read security.bpf.foo and trigger test_inode_getxattr again. |
| * This will remove xattr security.bpf.bar. |
| */ |
| read_and_validate_foo(skel); |
| validate_bar_removed(skel); |
| |
| ASSERT_TRUE(skel->bss->set_security_bpf_bar_success, "set_security_bpf_bar_success"); |
| ASSERT_TRUE(skel->bss->remove_security_bpf_bar_success, "remove_security_bpf_bar_success"); |
| ASSERT_TRUE(skel->bss->set_security_selinux_fail, "set_security_selinux_fail"); |
| ASSERT_TRUE(skel->bss->remove_security_selinux_fail, "remove_security_selinux_fail"); |
| |
| /* Second, test _locked version of the kfuncs, with setxattr */ |
| |
| /* Set security.bpf.foo and trigger test_inode_setxattr. This |
| * bpf program will set security.bpf.bar to "world". |
| */ |
| set_foo(skel); |
| validate_bar_match(skel); |
| |
| /* Set security.bpf.foo and trigger test_inode_setxattr again. |
| * This will remove xattr security.bpf.bar. |
| */ |
| set_foo(skel); |
| validate_bar_removed(skel); |
| |
| ASSERT_TRUE(skel->bss->locked_set_security_bpf_bar_success, |
| "locked_set_security_bpf_bar_success"); |
| ASSERT_TRUE(skel->bss->locked_remove_security_bpf_bar_success, |
| "locked_remove_security_bpf_bar_success"); |
| ASSERT_TRUE(skel->bss->locked_set_security_selinux_fail, |
| "locked_set_security_selinux_fail"); |
| ASSERT_TRUE(skel->bss->locked_remove_security_selinux_fail, |
| "locked_remove_security_selinux_fail"); |
| |
| out: |
| close(fd); |
| test_set_remove_xattr__destroy(skel); |
| remove(testfile); |
| } |
| |
| #ifndef SHA256_DIGEST_SIZE |
| #define SHA256_DIGEST_SIZE 32 |
| #endif |
| |
| static void test_fsverity(void) |
| { |
| struct fsverity_enable_arg arg = {0}; |
| struct test_fsverity *skel = NULL; |
| struct fsverity_digest *d; |
| int fd, err; |
| char buffer[4096]; |
| |
| fd = open(testfile, O_CREAT | O_RDWR, 0644); |
| if (!ASSERT_GE(fd, 0, "create_file")) |
| return; |
| |
| /* Write random buffer, so the file is not empty */ |
| err = write(fd, buffer, 4096); |
| if (!ASSERT_EQ(err, 4096, "write_file")) |
| goto out; |
| close(fd); |
| |
| /* Reopen read-only, otherwise FS_IOC_ENABLE_VERITY will fail */ |
| fd = open(testfile, O_RDONLY, 0644); |
| if (!ASSERT_GE(fd, 0, "open_file1")) |
| return; |
| |
| /* Enable fsverity for the file. |
| * If the file system doesn't support verity, this will fail. Skip |
| * the test in such case. |
| */ |
| arg.version = 1; |
| arg.hash_algorithm = FS_VERITY_HASH_ALG_SHA256; |
| arg.block_size = 4096; |
| err = ioctl(fd, FS_IOC_ENABLE_VERITY, &arg); |
| if (err) { |
| printf("%s:SKIP:local fs doesn't support fsverity (%d)\n" |
| "To run this test, try enable CONFIG_FS_VERITY and enable FSVerity for the filesystem.\n", |
| __func__, errno); |
| test__skip(); |
| goto out; |
| } |
| |
| skel = test_fsverity__open_and_load(); |
| if (!ASSERT_OK_PTR(skel, "test_fsverity__open_and_load")) |
| goto out; |
| |
| /* Get fsverity_digest from ioctl */ |
| d = (struct fsverity_digest *)skel->bss->expected_digest; |
| d->digest_algorithm = FS_VERITY_HASH_ALG_SHA256; |
| d->digest_size = SHA256_DIGEST_SIZE; |
| err = ioctl(fd, FS_IOC_MEASURE_VERITY, skel->bss->expected_digest); |
| if (!ASSERT_OK(err, "ioctl_FS_IOC_MEASURE_VERITY")) |
| goto out; |
| |
| skel->bss->monitored_pid = getpid(); |
| err = test_fsverity__attach(skel); |
| if (!ASSERT_OK(err, "test_fsverity__attach")) |
| goto out; |
| |
| /* Reopen the file to trigger the program */ |
| close(fd); |
| fd = open(testfile, O_RDONLY); |
| if (!ASSERT_GE(fd, 0, "open_file2")) |
| goto out; |
| |
| ASSERT_EQ(skel->bss->got_fsverity, 1, "got_fsverity"); |
| ASSERT_EQ(skel->bss->digest_matches, 1, "digest_matches"); |
| out: |
| close(fd); |
| test_fsverity__destroy(skel); |
| remove(testfile); |
| } |
| |
| void test_fs_kfuncs(void) |
| { |
| /* Matches xattr_names in progs/test_get_xattr.c */ |
| if (test__start_subtest("user_xattr")) |
| test_get_xattr("user.kfuncs", "hello", true); |
| |
| if (test__start_subtest("security_bpf_xattr")) |
| test_get_xattr("security.bpf.xxx", "hello", true); |
| |
| if (test__start_subtest("security_bpf_xattr_error")) |
| test_get_xattr("security.bpf", "hello", false); |
| |
| if (test__start_subtest("security_selinux_xattr_error")) |
| test_get_xattr("security.selinux", "hello", false); |
| |
| if (test__start_subtest("set_remove_xattr")) |
| test_set_remove_xattr(); |
| |
| if (test__start_subtest("fsverity")) |
| test_fsverity(); |
| } |