* [PATCH v4 bpf-next 0/6] Enable writing xattr from BPF programs
@ 2024-12-17 6:38 Song Liu
2024-12-17 6:38 ` [PATCH v4 bpf-next 1/6] fs/xattr: bpf: Introduce security.bpf. xattr name prefix Song Liu
` (5 more replies)
0 siblings, 6 replies; 12+ messages in thread
From: Song Liu @ 2024-12-17 6:38 UTC (permalink / raw)
To: bpf, linux-fsdevel, linux-kernel, linux-security-module
Cc: kernel-team, andrii, eddyz87, ast, daniel, martin.lau, viro,
brauner, jack, kpsingh, mattbobrowski, liamwisehart, shankaran,
Song Liu
Add support to set and remove xattr from BPF program. Also add
security.bpf. xattr name prefix.
kfuncs are added to set and remove xattrs with security.bpf. name
prefix. Update kfuncs bpf_get_[file|dentry]_xattr to read xattrs
with security.bpf. name prefix. Note that BPF programs can read
user. xattrs, but not write and remove them.
Cover letter of v1 and v2:
Follow up discussion in LPC 2024 [1], that we need security.bpf xattr
prefix. This set adds "security.bpf." xattr name prefix, and allows
bpf kfuncs bpf_get_[file|dentry]_xattr() to read these xattrs.
[1] https://lpc.events/event/18/contributions/1940/
Changes v3 => v4
1. Do write permission check with inode locked. (Jan Kara)
2. Fix some source_inline warnings.
v3: https://lore.kernel.org/bpf/20241210220627.2800362-1-song@kernel.org/
Changes v2 => v3
1. Add kfuncs to set and remove xattr from BPF programs.
v2: https://lore.kernel.org/bpf/20241016070955.375923-1-song@kernel.org/
Changes v1 => v2
1. Update comment of bpf_get_[file|dentry]_xattr. (Jiri Olsa)
2. Fix comment for return value of bpf_get_[file|dentry]_xattr.
v1: https://lore.kernel.org/bpf/20241002214637.3625277-1-song@kernel.org/
Song Liu (6):
fs/xattr: bpf: Introduce security.bpf. xattr name prefix
selftests/bpf: Extend test fs_kfuncs to cover security.bpf. xattr
names
bpf: lsm: Add two more sleepable hooks
bpf: fs/xattr: Add BPF kfuncs to set and remove xattrs
selftests/bpf: Test kfuncs that set and remove xattr from BPF programs
selftests/bpf: Add __failure tests for set/remove xattr kfuncs
fs/bpf_fs_kfuncs.c | 262 +++++++++++++++++-
include/uapi/linux/xattr.h | 4 +
kernel/bpf/bpf_lsm.c | 2 +
tools/testing/selftests/bpf/bpf_kfuncs.h | 10 +
.../selftests/bpf/prog_tests/fs_kfuncs.c | 165 ++++++++++-
.../selftests/bpf/progs/test_get_xattr.c | 28 +-
.../bpf/progs/test_set_remove_xattr.c | 129 +++++++++
.../bpf/progs/test_set_remove_xattr_failure.c | 56 ++++
8 files changed, 636 insertions(+), 20 deletions(-)
create mode 100644 tools/testing/selftests/bpf/progs/test_set_remove_xattr.c
create mode 100644 tools/testing/selftests/bpf/progs/test_set_remove_xattr_failure.c
--
2.43.5
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v4 bpf-next 1/6] fs/xattr: bpf: Introduce security.bpf. xattr name prefix
2024-12-17 6:38 [PATCH v4 bpf-next 0/6] Enable writing xattr from BPF programs Song Liu
@ 2024-12-17 6:38 ` Song Liu
2024-12-18 11:41 ` Jan Kara
2024-12-17 6:38 ` [PATCH v4 bpf-next 2/6] selftests/bpf: Extend test fs_kfuncs to cover security.bpf. xattr names Song Liu
` (4 subsequent siblings)
5 siblings, 1 reply; 12+ messages in thread
From: Song Liu @ 2024-12-17 6:38 UTC (permalink / raw)
To: bpf, linux-fsdevel, linux-kernel, linux-security-module
Cc: kernel-team, andrii, eddyz87, ast, daniel, martin.lau, viro,
brauner, jack, kpsingh, mattbobrowski, liamwisehart, shankaran,
Song Liu
Introduct new xattr name prefix security.bpf., and enable reading these
xattrs from bpf kfuncs bpf_get_[file|dentry]_xattr().
As we are on it, correct the comments for return value of
bpf_get_[file|dentry]_xattr(), i.e. return length the xattr value on
success.
Signed-off-by: Song Liu <song@kernel.org>
Acked-by: Christian Brauner <brauner@kernel.org>
---
fs/bpf_fs_kfuncs.c | 19 ++++++++++++++-----
include/uapi/linux/xattr.h | 4 ++++
2 files changed, 18 insertions(+), 5 deletions(-)
diff --git a/fs/bpf_fs_kfuncs.c b/fs/bpf_fs_kfuncs.c
index 3fe9f59ef867..8a65184c8c2c 100644
--- a/fs/bpf_fs_kfuncs.c
+++ b/fs/bpf_fs_kfuncs.c
@@ -93,6 +93,11 @@ __bpf_kfunc int bpf_path_d_path(struct path *path, char *buf, size_t buf__sz)
return len;
}
+static bool match_security_bpf_prefix(const char *name__str)
+{
+ return !strncmp(name__str, XATTR_NAME_BPF_LSM, XATTR_NAME_BPF_LSM_LEN);
+}
+
/**
* bpf_get_dentry_xattr - get xattr of a dentry
* @dentry: dentry to get xattr from
@@ -101,9 +106,10 @@ __bpf_kfunc int bpf_path_d_path(struct path *path, char *buf, size_t buf__sz)
*
* Get xattr *name__str* of *dentry* and store the output in *value_ptr*.
*
- * For security reasons, only *name__str* with prefix "user." is allowed.
+ * For security reasons, only *name__str* with prefix "user." or
+ * "security.bpf." is allowed.
*
- * Return: 0 on success, a negative value on error.
+ * Return: length of the xattr value on success, a negative value on error.
*/
__bpf_kfunc int bpf_get_dentry_xattr(struct dentry *dentry, const char *name__str,
struct bpf_dynptr *value_p)
@@ -117,7 +123,9 @@ __bpf_kfunc int bpf_get_dentry_xattr(struct dentry *dentry, const char *name__st
if (WARN_ON(!inode))
return -EINVAL;
- if (strncmp(name__str, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN))
+ /* Allow reading xattr with user. and security.bpf. prefix */
+ if (strncmp(name__str, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN) &&
+ !match_security_bpf_prefix(name__str))
return -EPERM;
value_len = __bpf_dynptr_size(value_ptr);
@@ -139,9 +147,10 @@ __bpf_kfunc int bpf_get_dentry_xattr(struct dentry *dentry, const char *name__st
*
* Get xattr *name__str* of *file* and store the output in *value_ptr*.
*
- * For security reasons, only *name__str* with prefix "user." is allowed.
+ * For security reasons, only *name__str* with prefix "user." or
+ * "security.bpf." is allowed.
*
- * Return: 0 on success, a negative value on error.
+ * Return: length of the xattr value on success, a negative value on error.
*/
__bpf_kfunc int bpf_get_file_xattr(struct file *file, const char *name__str,
struct bpf_dynptr *value_p)
diff --git a/include/uapi/linux/xattr.h b/include/uapi/linux/xattr.h
index 9854f9cff3c6..c7c85bb504ba 100644
--- a/include/uapi/linux/xattr.h
+++ b/include/uapi/linux/xattr.h
@@ -83,6 +83,10 @@ struct xattr_args {
#define XATTR_CAPS_SUFFIX "capability"
#define XATTR_NAME_CAPS XATTR_SECURITY_PREFIX XATTR_CAPS_SUFFIX
+#define XATTR_BPF_LSM_SUFFIX "bpf."
+#define XATTR_NAME_BPF_LSM (XATTR_SECURITY_PREFIX XATTR_BPF_LSM_SUFFIX)
+#define XATTR_NAME_BPF_LSM_LEN (sizeof(XATTR_NAME_BPF_LSM) - 1)
+
#define XATTR_POSIX_ACL_ACCESS "posix_acl_access"
#define XATTR_NAME_POSIX_ACL_ACCESS XATTR_SYSTEM_PREFIX XATTR_POSIX_ACL_ACCESS
#define XATTR_POSIX_ACL_DEFAULT "posix_acl_default"
--
2.43.5
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v4 bpf-next 2/6] selftests/bpf: Extend test fs_kfuncs to cover security.bpf. xattr names
2024-12-17 6:38 [PATCH v4 bpf-next 0/6] Enable writing xattr from BPF programs Song Liu
2024-12-17 6:38 ` [PATCH v4 bpf-next 1/6] fs/xattr: bpf: Introduce security.bpf. xattr name prefix Song Liu
@ 2024-12-17 6:38 ` Song Liu
2024-12-17 6:38 ` [PATCH v4 bpf-next 3/6] bpf: lsm: Add two more sleepable hooks Song Liu
` (3 subsequent siblings)
5 siblings, 0 replies; 12+ messages in thread
From: Song Liu @ 2024-12-17 6:38 UTC (permalink / raw)
To: bpf, linux-fsdevel, linux-kernel, linux-security-module
Cc: kernel-team, andrii, eddyz87, ast, daniel, martin.lau, viro,
brauner, jack, kpsingh, mattbobrowski, liamwisehart, shankaran,
Song Liu
Extend test_progs fs_kfuncs to cover different xattr names. Specifically:
xattr name "user.kfuncs" and "security.bpf.xxx" can be read from BPF
program with kfuncs bpf_get_[file|dentry]_xattr(); while "security.bpf"
and "security.selinux" cannot be read.
Signed-off-by: Song Liu <song@kernel.org>
---
.../selftests/bpf/prog_tests/fs_kfuncs.c | 37 ++++++++++++++-----
.../selftests/bpf/progs/test_get_xattr.c | 28 ++++++++++++--
2 files changed, 51 insertions(+), 14 deletions(-)
diff --git a/tools/testing/selftests/bpf/prog_tests/fs_kfuncs.c b/tools/testing/selftests/bpf/prog_tests/fs_kfuncs.c
index 5a0b51157451..419f45b56472 100644
--- a/tools/testing/selftests/bpf/prog_tests/fs_kfuncs.c
+++ b/tools/testing/selftests/bpf/prog_tests/fs_kfuncs.c
@@ -12,7 +12,7 @@
static const char testfile[] = "/tmp/test_progs_fs_kfuncs";
-static void test_xattr(void)
+static void test_get_xattr(const char *name, const char *value, bool allow_access)
{
struct test_get_xattr *skel = NULL;
int fd = -1, err;
@@ -25,7 +25,7 @@ static void test_xattr(void)
close(fd);
fd = -1;
- err = setxattr(testfile, "user.kfuncs", "hello", sizeof("hello"), 0);
+ 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",
@@ -48,16 +48,23 @@ static void test_xattr(void)
goto out;
fd = open(testfile, O_RDONLY, 0644);
+
if (!ASSERT_GE(fd, 0, "open_file"))
goto out;
- ASSERT_EQ(skel->bss->found_xattr_from_file, 1, "found_xattr_from_file");
-
/* Trigger security_inode_getxattr */
- err = getxattr(testfile, "user.kfuncs", v, sizeof(v));
- ASSERT_EQ(err, -1, "getxattr_return");
- ASSERT_EQ(errno, EINVAL, "getxattr_errno");
- ASSERT_EQ(skel->bss->found_xattr_from_dentry, 1, "found_xattr_from_dentry");
+ 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);
@@ -141,8 +148,18 @@ static void test_fsverity(void)
void test_fs_kfuncs(void)
{
- if (test__start_subtest("xattr"))
- test_xattr();
+ /* 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("fsverity"))
test_fsverity();
diff --git a/tools/testing/selftests/bpf/progs/test_get_xattr.c b/tools/testing/selftests/bpf/progs/test_get_xattr.c
index 66e737720f7c..358e3506e5b0 100644
--- a/tools/testing/selftests/bpf/progs/test_get_xattr.c
+++ b/tools/testing/selftests/bpf/progs/test_get_xattr.c
@@ -6,6 +6,7 @@
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include "bpf_kfuncs.h"
+#include "bpf_misc.h"
char _license[] SEC("license") = "GPL";
@@ -17,12 +18,23 @@ static const char expected_value[] = "hello";
char value1[32];
char value2[32];
+/* Matches caller of test_get_xattr() in prog_tests/fs_kfuncs.c */
+static const char * const xattr_names[] = {
+ /* The following work. */
+ "user.kfuncs",
+ "security.bpf.xxx",
+
+ /* The following do not work. */
+ "security.bpf",
+ "security.selinux"
+};
+
SEC("lsm.s/file_open")
int BPF_PROG(test_file_open, struct file *f)
{
struct bpf_dynptr value_ptr;
__u32 pid;
- int ret;
+ int ret, i;
pid = bpf_get_current_pid_tgid() >> 32;
if (pid != monitored_pid)
@@ -30,7 +42,11 @@ int BPF_PROG(test_file_open, struct file *f)
bpf_dynptr_from_mem(value1, sizeof(value1), 0, &value_ptr);
- ret = bpf_get_file_xattr(f, "user.kfuncs", &value_ptr);
+ for (i = 0; i < ARRAY_SIZE(xattr_names); i++) {
+ ret = bpf_get_file_xattr(f, xattr_names[i], &value_ptr);
+ if (ret == sizeof(expected_value))
+ break;
+ }
if (ret != sizeof(expected_value))
return 0;
if (bpf_strncmp(value1, ret, expected_value))
@@ -44,7 +60,7 @@ int BPF_PROG(test_inode_getxattr, struct dentry *dentry, char *name)
{
struct bpf_dynptr value_ptr;
__u32 pid;
- int ret;
+ int ret, i;
pid = bpf_get_current_pid_tgid() >> 32;
if (pid != monitored_pid)
@@ -52,7 +68,11 @@ int BPF_PROG(test_inode_getxattr, struct dentry *dentry, char *name)
bpf_dynptr_from_mem(value2, sizeof(value2), 0, &value_ptr);
- ret = bpf_get_dentry_xattr(dentry, "user.kfuncs", &value_ptr);
+ for (i = 0; i < ARRAY_SIZE(xattr_names); i++) {
+ ret = bpf_get_dentry_xattr(dentry, xattr_names[i], &value_ptr);
+ if (ret == sizeof(expected_value))
+ break;
+ }
if (ret != sizeof(expected_value))
return 0;
if (bpf_strncmp(value2, ret, expected_value))
--
2.43.5
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v4 bpf-next 3/6] bpf: lsm: Add two more sleepable hooks
2024-12-17 6:38 [PATCH v4 bpf-next 0/6] Enable writing xattr from BPF programs Song Liu
2024-12-17 6:38 ` [PATCH v4 bpf-next 1/6] fs/xattr: bpf: Introduce security.bpf. xattr name prefix Song Liu
2024-12-17 6:38 ` [PATCH v4 bpf-next 2/6] selftests/bpf: Extend test fs_kfuncs to cover security.bpf. xattr names Song Liu
@ 2024-12-17 6:38 ` Song Liu
2024-12-17 6:38 ` [PATCH v4 bpf-next 4/6] bpf: fs/xattr: Add BPF kfuncs to set and remove xattrs Song Liu
` (2 subsequent siblings)
5 siblings, 0 replies; 12+ messages in thread
From: Song Liu @ 2024-12-17 6:38 UTC (permalink / raw)
To: bpf, linux-fsdevel, linux-kernel, linux-security-module
Cc: kernel-team, andrii, eddyz87, ast, daniel, martin.lau, viro,
brauner, jack, kpsingh, mattbobrowski, liamwisehart, shankaran,
Song Liu
Add bpf_lsm_inode_removexattr and bpf_lsm_inode_post_removexattr to list
sleepable_lsm_hooks. These two hooks are always called from sleepable
context.
Signed-off-by: Song Liu <song@kernel.org>
---
kernel/bpf/bpf_lsm.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
index 967492b65185..0a59df1c550a 100644
--- a/kernel/bpf/bpf_lsm.c
+++ b/kernel/bpf/bpf_lsm.c
@@ -316,7 +316,9 @@ BTF_ID(func, bpf_lsm_inode_getxattr)
BTF_ID(func, bpf_lsm_inode_mknod)
BTF_ID(func, bpf_lsm_inode_need_killpriv)
BTF_ID(func, bpf_lsm_inode_post_setxattr)
+BTF_ID(func, bpf_lsm_inode_post_removexattr)
BTF_ID(func, bpf_lsm_inode_readlink)
+BTF_ID(func, bpf_lsm_inode_removexattr)
BTF_ID(func, bpf_lsm_inode_rename)
BTF_ID(func, bpf_lsm_inode_rmdir)
BTF_ID(func, bpf_lsm_inode_setattr)
--
2.43.5
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v4 bpf-next 4/6] bpf: fs/xattr: Add BPF kfuncs to set and remove xattrs
2024-12-17 6:38 [PATCH v4 bpf-next 0/6] Enable writing xattr from BPF programs Song Liu
` (2 preceding siblings ...)
2024-12-17 6:38 ` [PATCH v4 bpf-next 3/6] bpf: lsm: Add two more sleepable hooks Song Liu
@ 2024-12-17 6:38 ` Song Liu
2024-12-17 16:50 ` Alexei Starovoitov
2024-12-17 6:38 ` [PATCH v4 bpf-next 5/6] selftests/bpf: Test kfuncs that set and remove xattr from BPF programs Song Liu
2024-12-17 6:38 ` [PATCH v4 bpf-next 6/6] selftests/bpf: Add __failure tests for set/remove xattr kfuncs Song Liu
5 siblings, 1 reply; 12+ messages in thread
From: Song Liu @ 2024-12-17 6:38 UTC (permalink / raw)
To: bpf, linux-fsdevel, linux-kernel, linux-security-module
Cc: kernel-team, andrii, eddyz87, ast, daniel, martin.lau, viro,
brauner, jack, kpsingh, mattbobrowski, liamwisehart, shankaran,
Song Liu
Add the following kfuncs to set and remove xattrs from BPF programs:
bpf_set_dentry_xattr
bpf_remove_dentry_xattr
bpf_set_dentry_xattr_locked
bpf_remove_dentry_xattr_locked
The _locked version of these kfuncs are called from hooks where
dentry->d_inode is already locked.
Signed-off-by: Song Liu <song@kernel.org>
---
fs/bpf_fs_kfuncs.c | 243 ++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 242 insertions(+), 1 deletion(-)
diff --git a/fs/bpf_fs_kfuncs.c b/fs/bpf_fs_kfuncs.c
index 8a65184c8c2c..3261324e956c 100644
--- a/fs/bpf_fs_kfuncs.c
+++ b/fs/bpf_fs_kfuncs.c
@@ -6,6 +6,7 @@
#include <linux/btf_ids.h>
#include <linux/dcache.h>
#include <linux/fs.h>
+#include <linux/fsnotify.h>
#include <linux/file.h>
#include <linux/mm.h>
#include <linux/xattr.h>
@@ -161,6 +162,164 @@ __bpf_kfunc int bpf_get_file_xattr(struct file *file, const char *name__str,
return bpf_get_dentry_xattr(dentry, name__str, value_p);
}
+static int bpf_xattr_write_permission(const char *name, struct inode *inode)
+{
+ if (WARN_ON(!inode))
+ return -EINVAL;
+
+ /* Only allow setting and removing security.bpf. xattrs */
+ if (!match_security_bpf_prefix(name))
+ return -EPERM;
+
+ return inode_permission(&nop_mnt_idmap, inode, MAY_WRITE);
+}
+
+static int __bpf_set_dentry_xattr(struct dentry *dentry, const char *name,
+ const struct bpf_dynptr *value_p, int flags, bool lock_inode)
+{
+ struct bpf_dynptr_kern *value_ptr = (struct bpf_dynptr_kern *)value_p;
+ struct inode *inode = d_inode(dentry);
+ const void *value;
+ u32 value_len;
+ int ret;
+
+ value_len = __bpf_dynptr_size(value_ptr);
+ value = __bpf_dynptr_data(value_ptr, value_len);
+ if (!value)
+ return -EINVAL;
+
+ if (lock_inode)
+ inode_lock(inode);
+
+ ret = bpf_xattr_write_permission(name, inode);
+ if (ret)
+ goto out;
+
+ ret = __vfs_setxattr(&nop_mnt_idmap, dentry, inode, name,
+ value, value_len, flags);
+ if (!ret) {
+ fsnotify_xattr(dentry);
+
+ /* This xattr is set by BPF LSM, so we do not call
+ * security_inode_post_setxattr. This is the same as
+ * security_inode_setsecurity().
+ */
+ }
+out:
+ if (lock_inode)
+ inode_unlock(inode);
+ return ret;
+}
+
+/**
+ * bpf_set_dentry_xattr - set a xattr of a dentry
+ * @dentry: dentry to get xattr from
+ * @name__str: name of the xattr
+ * @value_p: xattr value
+ * @flags: flags to pass into filesystem operations
+ *
+ * Set xattr *name__str* of *dentry* to the value in *value_ptr*.
+ *
+ * For security reasons, only *name__str* with prefix "security.bpf."
+ * is allowed.
+ *
+ * The caller has not locked dentry->d_inode.
+ *
+ * Return: 0 on success, a negative value on error.
+ */
+__bpf_kfunc int bpf_set_dentry_xattr(struct dentry *dentry, const char *name__str,
+ const struct bpf_dynptr *value_p, int flags)
+{
+ return __bpf_set_dentry_xattr(dentry, name__str, value_p, flags, true);
+}
+
+/**
+ * bpf_set_dentry_xattr_locked - set a xattr of a dentry
+ * @dentry: dentry to get xattr from
+ * @name__str: name of the xattr
+ * @value_p: xattr value
+ * @flags: flags to pass into filesystem operations
+ *
+ * Set xattr *name__str* of *dentry* to the value in *value_ptr*.
+ *
+ * For security reasons, only *name__str* with prefix "security.bpf."
+ * is allowed.
+ *
+ * The caller already locked dentry->d_inode.
+ *
+ * Return: 0 on success, a negative value on error.
+ */
+__bpf_kfunc int bpf_set_dentry_xattr_locked(struct dentry *dentry, const char *name__str,
+ const struct bpf_dynptr *value_p, int flags)
+{
+ return __bpf_set_dentry_xattr(dentry, name__str, value_p, flags, false);
+}
+
+static int __bpf_remove_dentry_xattr(struct dentry *dentry, const char *name__str,
+ bool lock_inode)
+{
+ struct inode *inode = d_inode(dentry);
+ int ret;
+
+ if (lock_inode)
+ inode_lock(inode);
+
+ ret = bpf_xattr_write_permission(name__str, inode);
+ if (ret)
+ goto out;
+
+ ret = __vfs_removexattr(&nop_mnt_idmap, dentry, name__str);
+ if (!ret) {
+ fsnotify_xattr(dentry);
+
+ /* This xattr is removed by BPF LSM, so we do not call
+ * security_inode_post_removexattr.
+ */
+ }
+out:
+ if (lock_inode)
+ inode_unlock(inode);
+ return ret;
+}
+
+/**
+ * bpf_remove_dentry_xattr - remove a xattr of a dentry
+ * @dentry: dentry to get xattr from
+ * @name__str: name of the xattr
+ *
+ * Rmove xattr *name__str* of *dentry*.
+ *
+ * For security reasons, only *name__str* with prefix "security.bpf."
+ * is allowed.
+ *
+ * The caller has not locked dentry->d_inode.
+ *
+ * Return: 0 on success, a negative value on error.
+ */
+__bpf_kfunc int bpf_remove_dentry_xattr(struct dentry *dentry, const char *name__str)
+{
+ return __bpf_remove_dentry_xattr(dentry, name__str, true);
+}
+
+/**
+ * bpf_remove_dentry_xattr_locked - remove a xattr of a dentry
+ * @dentry: dentry to get xattr from
+ * @name__str: name of the xattr
+ *
+ * Rmove xattr *name__str* of *dentry*.
+ *
+ * For security reasons, only *name__str* with prefix "security.bpf."
+ * is allowed.
+ *
+ * The caller already locked dentry->d_inode.
+ *
+ * Return: 0 on success, a negative value on error.
+ */
+__bpf_kfunc int bpf_remove_dentry_xattr_locked(struct dentry *dentry, const char *name__str)
+{
+ return __bpf_remove_dentry_xattr(dentry, name__str, false);
+}
+
__bpf_kfunc_end_defs();
BTF_KFUNCS_START(bpf_fs_kfunc_set_ids)
@@ -186,9 +345,91 @@ static const struct btf_kfunc_id_set bpf_fs_kfunc_set = {
.filter = bpf_fs_kfuncs_filter,
};
+/* bpf_[set|remove]_dentry_xattr.* hooks have KF_TRUSTED_ARGS and
+ * KF_SLEEPABLE, so they are only available to sleepable hooks with
+ * dentry arguments.
+ *
+ * Setting and removing xattr requires exclusive lock on dentry->d_inode.
+ * Some hooks already locked d_inode, while some hooks have not locked
+ * d_inode. Therefore, we need different kfuncs for different hooks.
+ * Specifically, hooks in the following list (d_inode_locked_hooks)
+ * should call bpf_[set|remove]_dentry_xattr_locked; while other hooks
+ * should call bpf_[set|remove]_dentry_xattr.
+ */
+BTF_SET_START(d_inode_locked_hooks)
+BTF_ID(func, bpf_lsm_inode_post_removexattr)
+BTF_ID(func, bpf_lsm_inode_post_setattr)
+BTF_ID(func, bpf_lsm_inode_post_setxattr)
+BTF_ID(func, bpf_lsm_inode_removexattr)
+BTF_ID(func, bpf_lsm_inode_rmdir)
+BTF_ID(func, bpf_lsm_inode_setattr)
+BTF_ID(func, bpf_lsm_inode_setxattr)
+BTF_ID(func, bpf_lsm_inode_unlink)
+#ifdef CONFIG_SECURITY_PATH
+BTF_ID(func, bpf_lsm_path_unlink)
+BTF_ID(func, bpf_lsm_path_rmdir)
+#endif /* CONFIG_SECURITY_PATH */
+BTF_SET_END(d_inode_locked_hooks)
+
+static bool bpf_lsm_has_d_inode_locked(const struct bpf_prog *prog)
+{
+ return btf_id_set_contains(&d_inode_locked_hooks, prog->aux->attach_btf_id);
+}
+
+BTF_KFUNCS_START(bpf_write_xattr_set_ids)
+BTF_ID_FLAGS(func, bpf_set_dentry_xattr, KF_SLEEPABLE | KF_TRUSTED_ARGS)
+BTF_ID_FLAGS(func, bpf_remove_dentry_xattr, KF_SLEEPABLE | KF_TRUSTED_ARGS)
+BTF_KFUNCS_END(bpf_write_xattr_set_ids)
+
+static int bpf_write_xattr_filter(const struct bpf_prog *prog, u32 kfunc_id)
+{
+ if (!btf_id_set8_contains(&bpf_write_xattr_set_ids, kfunc_id))
+ return 0;
+ if (prog->type != BPF_PROG_TYPE_LSM)
+ return -EACCES;
+
+ if (bpf_lsm_has_d_inode_locked(prog))
+ return -EINVAL;
+ return 0;
+}
+
+static const struct btf_kfunc_id_set bpf_write_xattr_set = {
+ .owner = THIS_MODULE,
+ .set = &bpf_write_xattr_set_ids,
+ .filter = bpf_write_xattr_filter,
+};
+
+BTF_KFUNCS_START(bpf_write_xattr_locked_set_ids)
+BTF_ID_FLAGS(func, bpf_set_dentry_xattr_locked, KF_SLEEPABLE | KF_TRUSTED_ARGS)
+BTF_ID_FLAGS(func, bpf_remove_dentry_xattr_locked, KF_SLEEPABLE | KF_TRUSTED_ARGS)
+BTF_KFUNCS_END(bpf_write_xattr_locked_set_ids)
+
+static int bpf_write_xattr_locked_filter(const struct bpf_prog *prog, u32 kfunc_id)
+{
+ if (!btf_id_set8_contains(&bpf_write_xattr_locked_set_ids, kfunc_id))
+ return 0;
+ if (prog->type != BPF_PROG_TYPE_LSM)
+ return -EACCES;
+
+ if (!bpf_lsm_has_d_inode_locked(prog))
+ return -EINVAL;
+ return 0;
+}
+
+static const struct btf_kfunc_id_set bpf_write_xattr_locked_set = {
+ .owner = THIS_MODULE,
+ .set = &bpf_write_xattr_locked_set_ids,
+ .filter = bpf_write_xattr_locked_filter,
+};
+
static int __init bpf_fs_kfuncs_init(void)
{
- return register_btf_kfunc_id_set(BPF_PROG_TYPE_LSM, &bpf_fs_kfunc_set);
+ int ret;
+
+ ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_LSM, &bpf_fs_kfunc_set);
+ ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_LSM, &bpf_write_xattr_set);
+ ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_LSM, &bpf_write_xattr_locked_set);
+ return ret;
}
late_initcall(bpf_fs_kfuncs_init);
--
2.43.5
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v4 bpf-next 5/6] selftests/bpf: Test kfuncs that set and remove xattr from BPF programs
2024-12-17 6:38 [PATCH v4 bpf-next 0/6] Enable writing xattr from BPF programs Song Liu
` (3 preceding siblings ...)
2024-12-17 6:38 ` [PATCH v4 bpf-next 4/6] bpf: fs/xattr: Add BPF kfuncs to set and remove xattrs Song Liu
@ 2024-12-17 6:38 ` Song Liu
2024-12-17 6:38 ` [PATCH v4 bpf-next 6/6] selftests/bpf: Add __failure tests for set/remove xattr kfuncs Song Liu
5 siblings, 0 replies; 12+ messages in thread
From: Song Liu @ 2024-12-17 6:38 UTC (permalink / raw)
To: bpf, linux-fsdevel, linux-kernel, linux-security-module
Cc: kernel-team, andrii, eddyz87, ast, daniel, martin.lau, viro,
brauner, jack, kpsingh, mattbobrowski, liamwisehart, shankaran,
Song Liu
Two sets of tests are added to exercise the not _locked and _locked
version of the kfuncs. For both tests, user space accesses xattr
security.bpf.foo on a testfile. The BPF program is triggered by user
space access (on LSM hook inode_[set|get]_xattr) and sets or removes
xattr security.bpf.bar. Then user space then validates that xattr
security.bpf.bar is set or removed as expected.
Signed-off-by: Song Liu <song@kernel.org>
---
tools/testing/selftests/bpf/bpf_kfuncs.h | 10 ++
.../selftests/bpf/prog_tests/fs_kfuncs.c | 125 +++++++++++++++++
.../bpf/progs/test_set_remove_xattr.c | 129 ++++++++++++++++++
3 files changed, 264 insertions(+)
create mode 100644 tools/testing/selftests/bpf/progs/test_set_remove_xattr.c
diff --git a/tools/testing/selftests/bpf/bpf_kfuncs.h b/tools/testing/selftests/bpf/bpf_kfuncs.h
index 2eb3483f2fb0..e53f3f5304e2 100644
--- a/tools/testing/selftests/bpf/bpf_kfuncs.h
+++ b/tools/testing/selftests/bpf/bpf_kfuncs.h
@@ -87,4 +87,14 @@ struct dentry;
*/
extern int bpf_get_dentry_xattr(struct dentry *dentry, const char *name,
struct bpf_dynptr *value_ptr) __ksym __weak;
+
+extern int bpf_set_dentry_xattr_locked(struct dentry *dentry, const char *name__str,
+ const struct bpf_dynptr *value_p, int flags) __ksym __weak;
+extern int bpf_remove_dentry_xattr_locked(struct dentry *dentry,
+ const char *name__str) __ksym __weak;
+
+extern int bpf_set_dentry_xattr(struct dentry *dentry, const char *name__str,
+ const struct bpf_dynptr *value_p, int flags) __ksym __weak;
+extern int bpf_remove_dentry_xattr(struct dentry *dentry, const char *name__str) __ksym __weak;
+
#endif
diff --git a/tools/testing/selftests/bpf/prog_tests/fs_kfuncs.c b/tools/testing/selftests/bpf/prog_tests/fs_kfuncs.c
index 419f45b56472..43a26ec69a8e 100644
--- a/tools/testing/selftests/bpf/prog_tests/fs_kfuncs.c
+++ b/tools/testing/selftests/bpf/prog_tests/fs_kfuncs.c
@@ -8,6 +8,7 @@
#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";
@@ -72,6 +73,127 @@ static void test_get_xattr(const char *name, const char *value, bool allow_acces
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
@@ -161,6 +283,9 @@ void test_fs_kfuncs(void)
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();
}
diff --git a/tools/testing/selftests/bpf/progs/test_set_remove_xattr.c b/tools/testing/selftests/bpf/progs/test_set_remove_xattr.c
new file mode 100644
index 000000000000..3ed5b489edfa
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_set_remove_xattr.c
@@ -0,0 +1,129 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2024 Meta Platforms, Inc. and affiliates. */
+
+#include "vmlinux.h"
+#include <errno.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_kfuncs.h"
+#include "bpf_misc.h"
+
+char _license[] SEC("license") = "GPL";
+
+__u32 monitored_pid;
+
+const char xattr_foo[] = "security.bpf.foo";
+const char xattr_bar[] = "security.bpf.bar";
+const char xattr_linux[] = "security.selinux";
+char value_bar[] = "world";
+char read_value[32];
+
+bool set_security_bpf_bar_success;
+bool remove_security_bpf_bar_success;
+bool set_security_selinux_fail;
+bool remove_security_selinux_fail;
+
+char name_buf[32];
+
+static inline bool name_match_foo(const char *name)
+{
+ bpf_probe_read_kernel(name_buf, sizeof(name_buf), name);
+
+ return !bpf_strncmp(name_buf, sizeof(xattr_foo), xattr_foo);
+}
+
+/* Test bpf_set_dentry_xattr and bpf_remove_dentry_xattr */
+SEC("lsm.s/inode_getxattr")
+int BPF_PROG(test_inode_getxattr, struct dentry *dentry, char *name)
+{
+ struct bpf_dynptr value_ptr;
+ __u32 pid;
+ int ret;
+
+ pid = bpf_get_current_pid_tgid() >> 32;
+ if (pid != monitored_pid)
+ return 0;
+
+ /* Only do the following for security.bpf.foo */
+ if (!name_match_foo(name))
+ return 0;
+
+ bpf_dynptr_from_mem(read_value, sizeof(read_value), 0, &value_ptr);
+
+ /* read security.bpf.bar */
+ ret = bpf_get_dentry_xattr(dentry, xattr_bar, &value_ptr);
+
+ if (ret < 0) {
+ /* If security.bpf.bar doesn't exist, set it */
+ bpf_dynptr_from_mem(value_bar, sizeof(value_bar), 0, &value_ptr);
+
+ ret = bpf_set_dentry_xattr(dentry, xattr_bar, &value_ptr, 0);
+ if (!ret)
+ set_security_bpf_bar_success = true;
+ ret = bpf_set_dentry_xattr(dentry, xattr_linux, &value_ptr, 0);
+ if (ret)
+ set_security_selinux_fail = true;
+ } else {
+ /* If security.bpf.bar exists, remove it */
+ ret = bpf_remove_dentry_xattr(dentry, xattr_bar);
+ if (!ret)
+ remove_security_bpf_bar_success = true;
+
+ ret = bpf_remove_dentry_xattr(dentry, xattr_linux);
+ if (ret)
+ remove_security_selinux_fail = true;
+ }
+
+ return 0;
+}
+
+bool locked_set_security_bpf_bar_success;
+bool locked_remove_security_bpf_bar_success;
+bool locked_set_security_selinux_fail;
+bool locked_remove_security_selinux_fail;
+
+/* Test bpf_set_dentry_xattr_locked and bpf_remove_dentry_xattr_locked */
+SEC("lsm.s/inode_setxattr")
+int BPF_PROG(test_inode_setxattr, struct mnt_idmap *idmap,
+ struct dentry *dentry, const char *name,
+ const void *value, size_t size, int flags)
+{
+ struct bpf_dynptr value_ptr;
+ __u32 pid;
+ int ret;
+
+ pid = bpf_get_current_pid_tgid() >> 32;
+ if (pid != monitored_pid)
+ return 0;
+
+ /* Only do the following for security.bpf.foo */
+ if (!name_match_foo(name))
+ return 0;
+
+ bpf_dynptr_from_mem(read_value, sizeof(read_value), 0, &value_ptr);
+
+ /* read security.bpf.bar */
+ ret = bpf_get_dentry_xattr(dentry, xattr_bar, &value_ptr);
+
+ if (ret < 0) {
+ /* If security.bpf.bar doesn't exist, set it */
+ bpf_dynptr_from_mem(value_bar, sizeof(value_bar), 0, &value_ptr);
+
+ ret = bpf_set_dentry_xattr_locked(dentry, xattr_bar, &value_ptr, 0);
+ if (!ret)
+ locked_set_security_bpf_bar_success = true;
+ ret = bpf_set_dentry_xattr_locked(dentry, xattr_linux, &value_ptr, 0);
+ if (ret)
+ locked_set_security_selinux_fail = true;
+ } else {
+ /* If security.bpf.bar exists, remove it */
+ ret = bpf_remove_dentry_xattr_locked(dentry, xattr_bar);
+ if (!ret)
+ locked_remove_security_bpf_bar_success = true;
+
+ ret = bpf_remove_dentry_xattr_locked(dentry, xattr_linux);
+ if (ret)
+ locked_remove_security_selinux_fail = true;
+ }
+
+ return 0;
+}
--
2.43.5
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v4 bpf-next 6/6] selftests/bpf: Add __failure tests for set/remove xattr kfuncs
2024-12-17 6:38 [PATCH v4 bpf-next 0/6] Enable writing xattr from BPF programs Song Liu
` (4 preceding siblings ...)
2024-12-17 6:38 ` [PATCH v4 bpf-next 5/6] selftests/bpf: Test kfuncs that set and remove xattr from BPF programs Song Liu
@ 2024-12-17 6:38 ` Song Liu
5 siblings, 0 replies; 12+ messages in thread
From: Song Liu @ 2024-12-17 6:38 UTC (permalink / raw)
To: bpf, linux-fsdevel, linux-kernel, linux-security-module
Cc: kernel-team, andrii, eddyz87, ast, daniel, martin.lau, viro,
brauner, jack, kpsingh, mattbobrowski, liamwisehart, shankaran,
Song Liu
Different LSM hooks should call different versions of set/remove xattr
kfuncs (with _locked or not). Add __failure tests to make sure the
verifier can detect when the user uses the wrong kfuncs.
Signed-off-by: Song Liu <song@kernel.org>
---
.../selftests/bpf/prog_tests/fs_kfuncs.c | 3 +
.../bpf/progs/test_set_remove_xattr_failure.c | 56 +++++++++++++++++++
2 files changed, 59 insertions(+)
create mode 100644 tools/testing/selftests/bpf/progs/test_set_remove_xattr_failure.c
diff --git a/tools/testing/selftests/bpf/prog_tests/fs_kfuncs.c b/tools/testing/selftests/bpf/prog_tests/fs_kfuncs.c
index 43a26ec69a8e..f24285ae8d43 100644
--- a/tools/testing/selftests/bpf/prog_tests/fs_kfuncs.c
+++ b/tools/testing/selftests/bpf/prog_tests/fs_kfuncs.c
@@ -9,6 +9,7 @@
#include <test_progs.h>
#include "test_get_xattr.skel.h"
#include "test_set_remove_xattr.skel.h"
+#include "test_set_remove_xattr_failure.skel.h"
#include "test_fsverity.skel.h"
static const char testfile[] = "/tmp/test_progs_fs_kfuncs";
@@ -286,6 +287,8 @@ void test_fs_kfuncs(void)
if (test__start_subtest("set_remove_xattr"))
test_set_remove_xattr();
+ RUN_TESTS(test_set_remove_xattr_failure);
+
if (test__start_subtest("fsverity"))
test_fsverity();
}
diff --git a/tools/testing/selftests/bpf/progs/test_set_remove_xattr_failure.c b/tools/testing/selftests/bpf/progs/test_set_remove_xattr_failure.c
new file mode 100644
index 000000000000..ee9c7df27a93
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_set_remove_xattr_failure.c
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2024 Meta Platforms, Inc. and affiliates. */
+
+#include "vmlinux.h"
+#include <bpf/bpf_tracing.h>
+#include "bpf_kfuncs.h"
+#include "bpf_misc.h"
+
+char _license[] SEC("license") = "GPL";
+
+static const char xattr_bar[] = "security.bpf.bar";
+char v[32];
+
+SEC("lsm.s/inode_getxattr")
+__failure __msg("calling kernel function bpf_set_dentry_xattr_locked is not allowed")
+int BPF_PROG(test_getxattr_failure_a, struct dentry *dentry, char *name)
+{
+ struct bpf_dynptr value_ptr;
+
+ bpf_dynptr_from_mem(v, sizeof(v), 0, &value_ptr);
+
+ bpf_set_dentry_xattr_locked(dentry, xattr_bar, &value_ptr, 0);
+ return 0;
+}
+
+SEC("lsm.s/inode_getxattr")
+__failure __msg("calling kernel function bpf_remove_dentry_xattr_locked is not allowed")
+int BPF_PROG(test_getxattr_failure_b, struct dentry *dentry, char *name)
+{
+ bpf_remove_dentry_xattr_locked(dentry, xattr_bar);
+ return 0;
+}
+
+SEC("lsm.s/inode_setxattr")
+__failure __msg("calling kernel function bpf_set_dentry_xattr is not allowed")
+int BPF_PROG(test_inode_setxattr_failure_a, struct mnt_idmap *idmap,
+ struct dentry *dentry, const char *name,
+ const void *value, size_t size, int flags)
+{
+ struct bpf_dynptr value_ptr;
+
+ bpf_dynptr_from_mem(v, sizeof(v), 0, &value_ptr);
+
+ bpf_set_dentry_xattr(dentry, xattr_bar, &value_ptr, 0);
+ return 0;
+}
+
+SEC("lsm.s/inode_setxattr")
+__failure __msg("calling kernel function bpf_remove_dentry_xattr is not allowed")
+int BPF_PROG(test_inode_setxattr_failure_b, struct mnt_idmap *idmap,
+ struct dentry *dentry, const char *name,
+ const void *value, size_t size, int flags)
+{
+ bpf_remove_dentry_xattr(dentry, xattr_bar);
+ return 0;
+}
--
2.43.5
^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH v4 bpf-next 4/6] bpf: fs/xattr: Add BPF kfuncs to set and remove xattrs
2024-12-17 6:38 ` [PATCH v4 bpf-next 4/6] bpf: fs/xattr: Add BPF kfuncs to set and remove xattrs Song Liu
@ 2024-12-17 16:50 ` Alexei Starovoitov
2024-12-17 18:24 ` Song Liu
0 siblings, 1 reply; 12+ messages in thread
From: Alexei Starovoitov @ 2024-12-17 16:50 UTC (permalink / raw)
To: Song Liu
Cc: bpf, Linux-Fsdevel, LKML, LSM List, Kernel Team, Andrii Nakryiko,
Eddy Z, Alexei Starovoitov, Daniel Borkmann, Martin KaFai Lau,
Alexander Viro, Christian Brauner, Jan Kara, KP Singh,
Matt Bobrowski, liamwisehart, shankaran
On Mon, Dec 16, 2024 at 10:38 PM Song Liu <song@kernel.org> wrote:
>
> Add the following kfuncs to set and remove xattrs from BPF programs:
>
> bpf_set_dentry_xattr
> bpf_remove_dentry_xattr
> bpf_set_dentry_xattr_locked
> bpf_remove_dentry_xattr_locked
>
> The _locked version of these kfuncs are called from hooks where
> dentry->d_inode is already locked.
...
> + *
> + * Setting and removing xattr requires exclusive lock on dentry->d_inode.
> + * Some hooks already locked d_inode, while some hooks have not locked
> + * d_inode. Therefore, we need different kfuncs for different hooks.
> + * Specifically, hooks in the following list (d_inode_locked_hooks)
> + * should call bpf_[set|remove]_dentry_xattr_locked; while other hooks
> + * should call bpf_[set|remove]_dentry_xattr.
> + */
the inode locking rules might change, so let's hide this
implementation detail from the bpf progs by making kfunc polymorphic.
To struct bpf_prog_aux add:
bool use_locked_kfunc:1;
and set it in bpf_check_attach_target() if it's attaching
to one of d_inode_locked_hooks
Then in fixup_kfunc_call() call some helper that
if (prog->aux->use_locked_kfunc &&
insn->imm == special_kfunc_list[KF_bpf_remove_dentry_xattr])
insn->imm = special_kfunc_list[KF_bpf_remove_dentry_xattr_locked];
The progs will be simpler and will suffer less churn
when the kernel side changes.
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v4 bpf-next 4/6] bpf: fs/xattr: Add BPF kfuncs to set and remove xattrs
2024-12-17 16:50 ` Alexei Starovoitov
@ 2024-12-17 18:24 ` Song Liu
2024-12-17 18:32 ` Kumar Kartikeya Dwivedi
0 siblings, 1 reply; 12+ messages in thread
From: Song Liu @ 2024-12-17 18:24 UTC (permalink / raw)
To: Alexei Starovoitov
Cc: Song Liu, bpf, Linux-Fsdevel, LKML, LSM List, Kernel Team,
Andrii Nakryiko, Eddy Z, Alexei Starovoitov, Daniel Borkmann,
Martin KaFai Lau, Alexander Viro, Christian Brauner, Jan Kara,
KP Singh, Matt Bobrowski, Liam Wisehart, Shankaran Gnanashanmugam
Hi Alexei,
Thanks for the review!
> On Dec 17, 2024, at 8:50 AM, Alexei Starovoitov <alexei.starovoitov@gmail.com> wrote:
>
> On Mon, Dec 16, 2024 at 10:38 PM Song Liu <song@kernel.org> wrote:
>>
>> Add the following kfuncs to set and remove xattrs from BPF programs:
>>
>> bpf_set_dentry_xattr
>> bpf_remove_dentry_xattr
>> bpf_set_dentry_xattr_locked
>> bpf_remove_dentry_xattr_locked
>>
>> The _locked version of these kfuncs are called from hooks where
>> dentry->d_inode is already locked.
>
> ...
>
>> + *
>> + * Setting and removing xattr requires exclusive lock on dentry->d_inode.
>> + * Some hooks already locked d_inode, while some hooks have not locked
>> + * d_inode. Therefore, we need different kfuncs for different hooks.
>> + * Specifically, hooks in the following list (d_inode_locked_hooks)
>> + * should call bpf_[set|remove]_dentry_xattr_locked; while other hooks
>> + * should call bpf_[set|remove]_dentry_xattr.
>> + */
>
> the inode locking rules might change, so let's hide this
> implementation detail from the bpf progs by making kfunc polymorphic.
>
> To struct bpf_prog_aux add:
> bool use_locked_kfunc:1;
> and set it in bpf_check_attach_target() if it's attaching
> to one of d_inode_locked_hooks
>
> Then in fixup_kfunc_call() call some helper that
> if (prog->aux->use_locked_kfunc &&
> insn->imm == special_kfunc_list[KF_bpf_remove_dentry_xattr])
> insn->imm = special_kfunc_list[KF_bpf_remove_dentry_xattr_locked];
>
> The progs will be simpler and will suffer less churn
> when the kernel side changes.
I was thinking about something in similar direction.
If we do this, shall we somehow hide the _locked version of the
kfuncs, so that the user cannot use it? If so, what's the best
way to do it?
Thanks,
Song
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v4 bpf-next 4/6] bpf: fs/xattr: Add BPF kfuncs to set and remove xattrs
2024-12-17 18:24 ` Song Liu
@ 2024-12-17 18:32 ` Kumar Kartikeya Dwivedi
2024-12-18 4:38 ` Song Liu
0 siblings, 1 reply; 12+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2024-12-17 18:32 UTC (permalink / raw)
To: Song Liu
Cc: Alexei Starovoitov, Song Liu, bpf, Linux-Fsdevel, LKML, LSM List,
Kernel Team, Andrii Nakryiko, Eddy Z, Alexei Starovoitov,
Daniel Borkmann, Martin KaFai Lau, Alexander Viro,
Christian Brauner, Jan Kara, KP Singh, Matt Bobrowski,
Liam Wisehart, Shankaran Gnanashanmugam
On Tue, 17 Dec 2024 at 19:25, Song Liu <songliubraving@meta.com> wrote:
>
> Hi Alexei,
>
> Thanks for the review!
>
> > On Dec 17, 2024, at 8:50 AM, Alexei Starovoitov <alexei.starovoitov@gmail.com> wrote:
> >
> > On Mon, Dec 16, 2024 at 10:38 PM Song Liu <song@kernel.org> wrote:
> >>
> >> Add the following kfuncs to set and remove xattrs from BPF programs:
> >>
> >> bpf_set_dentry_xattr
> >> bpf_remove_dentry_xattr
> >> bpf_set_dentry_xattr_locked
> >> bpf_remove_dentry_xattr_locked
> >>
> >> The _locked version of these kfuncs are called from hooks where
> >> dentry->d_inode is already locked.
> >
> > ...
> >
> >> + *
> >> + * Setting and removing xattr requires exclusive lock on dentry->d_inode.
> >> + * Some hooks already locked d_inode, while some hooks have not locked
> >> + * d_inode. Therefore, we need different kfuncs for different hooks.
> >> + * Specifically, hooks in the following list (d_inode_locked_hooks)
> >> + * should call bpf_[set|remove]_dentry_xattr_locked; while other hooks
> >> + * should call bpf_[set|remove]_dentry_xattr.
> >> + */
> >
> > the inode locking rules might change, so let's hide this
> > implementation detail from the bpf progs by making kfunc polymorphic.
> >
> > To struct bpf_prog_aux add:
> > bool use_locked_kfunc:1;
> > and set it in bpf_check_attach_target() if it's attaching
> > to one of d_inode_locked_hooks
> >
> > Then in fixup_kfunc_call() call some helper that
> > if (prog->aux->use_locked_kfunc &&
> > insn->imm == special_kfunc_list[KF_bpf_remove_dentry_xattr])
> > insn->imm = special_kfunc_list[KF_bpf_remove_dentry_xattr_locked];
> >
> > The progs will be simpler and will suffer less churn
> > when the kernel side changes.
>
> I was thinking about something in similar direction.
>
> If we do this, shall we somehow hide the _locked version of the
> kfuncs, so that the user cannot use it? If so, what's the best
> way to do it?
Just don't add BTF_ID_FLAGS entries for them.
You'd also need to make an extra call to add_kfunc_call to add its
details before you can do the fixup.
That allows find_kfunc_desc to work.
I did something similar in earlier versions of resilient locks.
In add_kfunc_call's end (instead of directly returning):
func_id = get_shadow_kfunc_id(func_id, offset);
if (!func_id)
return err;
return add_kfunc_call(env, func_id, offset);
Then check in fixup_kfunc_call to find shadow kfunc id and substitute imm.
Can use some other naming instead of "shadow".
Probably need to take a prog pointer to make a decision to find the
underlying kfunc id in your case.
>
> Thanks,
> Song
>
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v4 bpf-next 4/6] bpf: fs/xattr: Add BPF kfuncs to set and remove xattrs
2024-12-17 18:32 ` Kumar Kartikeya Dwivedi
@ 2024-12-18 4:38 ` Song Liu
0 siblings, 0 replies; 12+ messages in thread
From: Song Liu @ 2024-12-18 4:38 UTC (permalink / raw)
To: Kumar Kartikeya Dwivedi
Cc: Song Liu, Alexei Starovoitov, Song Liu, bpf, Linux-Fsdevel, LKML,
LSM List, Kernel Team, Andrii Nakryiko, Eddy Z,
Alexei Starovoitov, Daniel Borkmann, Martin KaFai Lau,
Alexander Viro, Christian Brauner, Jan Kara, KP Singh,
Matt Bobrowski, Liam Wisehart, Shankaran Gnanashanmugam
> On Dec 17, 2024, at 10:32 AM, Kumar Kartikeya Dwivedi <memxor@gmail.com> wrote:
>
> On Tue, 17 Dec 2024 at 19:25, Song Liu <songliubraving@meta.com> wrote:
>>
>> Hi Alexei,
>>
>> Thanks for the review!
>>
>>> On Dec 17, 2024, at 8:50 AM, Alexei Starovoitov <alexei.starovoitov@gmail.com> wrote:
>>>
>>> On Mon, Dec 16, 2024 at 10:38 PM Song Liu <song@kernel.org> wrote:
>>>>
>>>> Add the following kfuncs to set and remove xattrs from BPF programs:
>>>>
>>>> bpf_set_dentry_xattr
>>>> bpf_remove_dentry_xattr
>>>> bpf_set_dentry_xattr_locked
>>>> bpf_remove_dentry_xattr_locked
>>>>
>>>> The _locked version of these kfuncs are called from hooks where
>>>> dentry->d_inode is already locked.
>>>
>>> ...
>>>
>>>> + *
>>>> + * Setting and removing xattr requires exclusive lock on dentry->d_inode.
>>>> + * Some hooks already locked d_inode, while some hooks have not locked
>>>> + * d_inode. Therefore, we need different kfuncs for different hooks.
>>>> + * Specifically, hooks in the following list (d_inode_locked_hooks)
>>>> + * should call bpf_[set|remove]_dentry_xattr_locked; while other hooks
>>>> + * should call bpf_[set|remove]_dentry_xattr.
>>>> + */
>>>
>>> the inode locking rules might change, so let's hide this
>>> implementation detail from the bpf progs by making kfunc polymorphic.
>>>
>>> To struct bpf_prog_aux add:
>>> bool use_locked_kfunc:1;
>>> and set it in bpf_check_attach_target() if it's attaching
>>> to one of d_inode_locked_hooks
>>>
>>> Then in fixup_kfunc_call() call some helper that
>>> if (prog->aux->use_locked_kfunc &&
>>> insn->imm == special_kfunc_list[KF_bpf_remove_dentry_xattr])
>>> insn->imm = special_kfunc_list[KF_bpf_remove_dentry_xattr_locked];
>>>
>>> The progs will be simpler and will suffer less churn
>>> when the kernel side changes.
>>
>> I was thinking about something in similar direction.
>>
>> If we do this, shall we somehow hide the _locked version of the
>> kfuncs, so that the user cannot use it? If so, what's the best
>> way to do it?
>
> Just don't add BTF_ID_FLAGS entries for them.
> You'd also need to make an extra call to add_kfunc_call to add its
> details before you can do the fixup.
> That allows find_kfunc_desc to work.
> I did something similar in earlier versions of resilient locks.
> In add_kfunc_call's end (instead of directly returning):
> func_id = get_shadow_kfunc_id(func_id, offset);
> if (!func_id)
> return err;
> return add_kfunc_call(env, func_id, offset);
>
> Then check in fixup_kfunc_call to find shadow kfunc id and substitute imm.
> Can use some other naming instead of "shadow".
> Probably need to take a prog pointer to make a decision to find the
> underlying kfunc id in your case.
Thanks for the hints! They helped a lot.
I ended up doing this with a slightly different logic, which I
think is cleaner. I will send v5 shortly.
Song
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v4 bpf-next 1/6] fs/xattr: bpf: Introduce security.bpf. xattr name prefix
2024-12-17 6:38 ` [PATCH v4 bpf-next 1/6] fs/xattr: bpf: Introduce security.bpf. xattr name prefix Song Liu
@ 2024-12-18 11:41 ` Jan Kara
0 siblings, 0 replies; 12+ messages in thread
From: Jan Kara @ 2024-12-18 11:41 UTC (permalink / raw)
To: Song Liu
Cc: bpf, linux-fsdevel, linux-kernel, linux-security-module,
kernel-team, andrii, eddyz87, ast, daniel, martin.lau, viro,
brauner, jack, kpsingh, mattbobrowski, liamwisehart, shankaran
On Mon 16-12-24 22:38:16, Song Liu wrote:
> Introduct new xattr name prefix security.bpf., and enable reading these
> xattrs from bpf kfuncs bpf_get_[file|dentry]_xattr().
>
> As we are on it, correct the comments for return value of
> bpf_get_[file|dentry]_xattr(), i.e. return length the xattr value on
> success.
>
> Signed-off-by: Song Liu <song@kernel.org>
> Acked-by: Christian Brauner <brauner@kernel.org>
Looks good to me. Feel free to add:
Reviewed-by: Jan Kara <jack@suse.cz>
Honza
> ---
> fs/bpf_fs_kfuncs.c | 19 ++++++++++++++-----
> include/uapi/linux/xattr.h | 4 ++++
> 2 files changed, 18 insertions(+), 5 deletions(-)
>
> diff --git a/fs/bpf_fs_kfuncs.c b/fs/bpf_fs_kfuncs.c
> index 3fe9f59ef867..8a65184c8c2c 100644
> --- a/fs/bpf_fs_kfuncs.c
> +++ b/fs/bpf_fs_kfuncs.c
> @@ -93,6 +93,11 @@ __bpf_kfunc int bpf_path_d_path(struct path *path, char *buf, size_t buf__sz)
> return len;
> }
>
> +static bool match_security_bpf_prefix(const char *name__str)
> +{
> + return !strncmp(name__str, XATTR_NAME_BPF_LSM, XATTR_NAME_BPF_LSM_LEN);
> +}
> +
> /**
> * bpf_get_dentry_xattr - get xattr of a dentry
> * @dentry: dentry to get xattr from
> @@ -101,9 +106,10 @@ __bpf_kfunc int bpf_path_d_path(struct path *path, char *buf, size_t buf__sz)
> *
> * Get xattr *name__str* of *dentry* and store the output in *value_ptr*.
> *
> - * For security reasons, only *name__str* with prefix "user." is allowed.
> + * For security reasons, only *name__str* with prefix "user." or
> + * "security.bpf." is allowed.
> *
> - * Return: 0 on success, a negative value on error.
> + * Return: length of the xattr value on success, a negative value on error.
> */
> __bpf_kfunc int bpf_get_dentry_xattr(struct dentry *dentry, const char *name__str,
> struct bpf_dynptr *value_p)
> @@ -117,7 +123,9 @@ __bpf_kfunc int bpf_get_dentry_xattr(struct dentry *dentry, const char *name__st
> if (WARN_ON(!inode))
> return -EINVAL;
>
> - if (strncmp(name__str, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN))
> + /* Allow reading xattr with user. and security.bpf. prefix */
> + if (strncmp(name__str, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN) &&
> + !match_security_bpf_prefix(name__str))
> return -EPERM;
>
> value_len = __bpf_dynptr_size(value_ptr);
> @@ -139,9 +147,10 @@ __bpf_kfunc int bpf_get_dentry_xattr(struct dentry *dentry, const char *name__st
> *
> * Get xattr *name__str* of *file* and store the output in *value_ptr*.
> *
> - * For security reasons, only *name__str* with prefix "user." is allowed.
> + * For security reasons, only *name__str* with prefix "user." or
> + * "security.bpf." is allowed.
> *
> - * Return: 0 on success, a negative value on error.
> + * Return: length of the xattr value on success, a negative value on error.
> */
> __bpf_kfunc int bpf_get_file_xattr(struct file *file, const char *name__str,
> struct bpf_dynptr *value_p)
> diff --git a/include/uapi/linux/xattr.h b/include/uapi/linux/xattr.h
> index 9854f9cff3c6..c7c85bb504ba 100644
> --- a/include/uapi/linux/xattr.h
> +++ b/include/uapi/linux/xattr.h
> @@ -83,6 +83,10 @@ struct xattr_args {
> #define XATTR_CAPS_SUFFIX "capability"
> #define XATTR_NAME_CAPS XATTR_SECURITY_PREFIX XATTR_CAPS_SUFFIX
>
> +#define XATTR_BPF_LSM_SUFFIX "bpf."
> +#define XATTR_NAME_BPF_LSM (XATTR_SECURITY_PREFIX XATTR_BPF_LSM_SUFFIX)
> +#define XATTR_NAME_BPF_LSM_LEN (sizeof(XATTR_NAME_BPF_LSM) - 1)
> +
> #define XATTR_POSIX_ACL_ACCESS "posix_acl_access"
> #define XATTR_NAME_POSIX_ACL_ACCESS XATTR_SYSTEM_PREFIX XATTR_POSIX_ACL_ACCESS
> #define XATTR_POSIX_ACL_DEFAULT "posix_acl_default"
> --
> 2.43.5
>
--
Jan Kara <jack@suse.com>
SUSE Labs, CR
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2024-12-18 11:41 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-12-17 6:38 [PATCH v4 bpf-next 0/6] Enable writing xattr from BPF programs Song Liu
2024-12-17 6:38 ` [PATCH v4 bpf-next 1/6] fs/xattr: bpf: Introduce security.bpf. xattr name prefix Song Liu
2024-12-18 11:41 ` Jan Kara
2024-12-17 6:38 ` [PATCH v4 bpf-next 2/6] selftests/bpf: Extend test fs_kfuncs to cover security.bpf. xattr names Song Liu
2024-12-17 6:38 ` [PATCH v4 bpf-next 3/6] bpf: lsm: Add two more sleepable hooks Song Liu
2024-12-17 6:38 ` [PATCH v4 bpf-next 4/6] bpf: fs/xattr: Add BPF kfuncs to set and remove xattrs Song Liu
2024-12-17 16:50 ` Alexei Starovoitov
2024-12-17 18:24 ` Song Liu
2024-12-17 18:32 ` Kumar Kartikeya Dwivedi
2024-12-18 4:38 ` Song Liu
2024-12-17 6:38 ` [PATCH v4 bpf-next 5/6] selftests/bpf: Test kfuncs that set and remove xattr from BPF programs Song Liu
2024-12-17 6:38 ` [PATCH v4 bpf-next 6/6] selftests/bpf: Add __failure tests for set/remove xattr kfuncs Song Liu
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).