Skip to contentSkip to conversation

bpf: introduce BPF_CGROUP_FILE_OPEN

PATCH NAK Alexei Starovoitov 7 participants 19 messages Oct 4, 2018 – Oct 8, 2018

Subsystems (13)
lore
Share & tools
lore

Alexei Starovoitov

author maintainer
#0 lore raw

cover bpf: introduce BPF_CGROUP_FILE_OPEN

Hi All,

Similar to networking sandboxing programs and cgroup-v2 based hooks (BPF_CGROUP_INET_[INGRESS|EGRESS,] BPF_CGROUP_INET[4|6]_[BIND|CONNECT], etc) introduce basic per-container sandboxing for file access via
new BPF_PROG_TYPE_FILE_FILTER program type that attaches after security_file_open() LSM hook and works as additional file_open filter. The new cgroup bpf hook is called BPF_CGROUP_FILE_OPEN.

Just like other cgroup-bpf programs new BPF_PROG_TYPE_FILE_FILTER type is only available to root.

Use cases:
- disallow certain FS types within containers (fs_magic == CGROUP2_SUPER_MAGIC)
- restrict permissions in particular mount (mnt_id == X && (flags & O_RDWR))
- disallow access to hard linked sensitive files (nlink > 1 && mode == 0700)
- disallow access to world writeable files (mode == 0..7)
- disallow access to given set of files (dev_major == X && dev_minor == Y && inode == Z)

Alexei Starovoitov (6):
bpf: introduce BPF_PROG_TYPE_FILE_FILTER
fs: wire in BPF_CGROUP_FILE_OPEN hook tools/bpf: sync uapi/bpf.h
trace/bpf: allow %o modifier in bpf_trace_printk
libbpf: support BPF_CGROUP_FILE_OPEN in libbpf selftests/bpf: add a test for BPF_CGROUP_FILE_OPEN

 fs/open.c                                     |   4 +
 include/linux/bpf-cgroup.h                    |  10 +
 include/linux/bpf_types.h                     |   1 +
 include/uapi/linux/bpf.h                      |  28 ++-
 kernel/bpf/cgroup.c                           | 171 ++++++++++++++++++
 kernel/bpf/syscall.c                          |   7 +
 kernel/trace/bpf_trace.c                      |   2 +-
 tools/include/uapi/linux/bpf.h                |  28 ++-
 tools/lib/bpf/libbpf.c                        |   3 +
 tools/testing/selftests/bpf/.gitignore        |   1 +
 tools/testing/selftests/bpf/Makefile          |   6 +-
 tools/testing/selftests/bpf/bpf_helpers.h     |   2 +
 tools/testing/selftests/bpf/test_file_open.c  | 154 ++++++++++++++++
 .../selftests/bpf/test_file_open_common.h     |  13 ++
 .../selftests/bpf/test_file_open_kern.c       |  48 +++++
 15 files changed, 473 insertions(+), 5 deletions(-)
 create mode 100644 tools/testing/selftests/bpf/test_file_open.c
 create mode 100644 tools/testing/selftests/bpf/test_file_open_common.h
 create mode 100644 tools/testing/selftests/bpf/test_file_open_kern.c
Signature · 1 line
2.17.1
author maintainer
#1 lore raw

PATCH 1/6 bpf: introduce BPF_PROG_TYPE_FILE_FILTER

Similar to networking sandboxing programs and cgroup-v2 based hooks (BPF_CGROUP_INET_[INGRESS|EGRESS,] BPF_CGROUP_INET[4|6]_[BIND|CONNECT], etc) introduce basic per-container sandboxing for file access via
new BPF_PROG_TYPE_FILE_FILTER program type that attaches after security_file_open() LSM hook and works as additional file_open filter. The new cgroup bpf hook is called BPF_CGROUP_FILE_OPEN.

Just like other cgroup-bpf programs new BPF_PROG_TYPE_FILE_FILTER type is only available to root.

This program type has access to single argument 'struct bpf_file_info' that contains standard sys_stat fields:

struct bpf_file_info {
        __u64 inode;
        __u32 dev_major;
        __u32 dev_minor;
        __u32 fs_magic;
        __u32 mnt_id;
        __u32 nlink;
        __u32 mode;     /* file mode S_ISDIR, S_ISLNK, 0755, etc */
        __u32 flags;    /* open flags O_RDWR, O_CREAT, etc */
};

Other file attributes can be added in the future to the end of this struct without breaking bpf programs.

For debugging introduce bpf_get_file_path() helper that returns NUL-terminated full path of the file. It should never be used for sandboxing.

Use cases:
- disallow certain FS types within containers (fs_magic == CGROUP2_SUPER_MAGIC)
- restrict permissions in particular mount (mnt_id == X && (flags & O_RDWR))
- disallow access to hard linked sensitive files (nlink > 1 && mode == 0700)
- disallow access to world writeable files (mode == 0..7)
- disallow access to given set of files (dev_major == X && dev_minor == Y && inode == Z)

---
 include/linux/bpf-cgroup.h |  10 +++
 include/linux/bpf_types.h  |   1 +
 include/uapi/linux/bpf.h   |  28 +++++-
 kernel/bpf/cgroup.c        | 171 +++++++++++++++++++++++++++++++++++++
 kernel/bpf/syscall.c       |   7 ++
 5 files changed, 216 insertions(+), 1 deletion(-)
include/linux/bpf-cgroup.h +10 -0
index 588dd5f0bd85..766f0223c222 100644
@@ -109,6 +109,8 @@ int __cgroup_bpf_run_filter_sock_ops(struct sock *sk,
 int __cgroup_bpf_check_dev_permission(short dev_type, u32 major, u32 minor,
 				      short access, enum bpf_attach_type type);
 
+int __cgroup_bpf_file_filter(struct file *file, enum bpf_attach_type type);
+
 static inline enum bpf_cgroup_storage_type cgroup_storage_type(
 	struct bpf_map *map)
 {
@@ -253,6 +255,13 @@ int bpf_percpu_cgroup_storage_update(struct bpf_map *map, void *key,
 									      \
 	__ret;								      \
 })
+#define BPF_CGROUP_RUN_PROG_FILE_FILTER(file)				     \
+({									      \
+	int __ret = 0;							      \
+	if (cgroup_bpf_enabled)						      \
+		__ret = __cgroup_bpf_file_filter(file, BPF_CGROUP_FILE_OPEN); \
+	__ret;								      \
+})
 int cgroup_bpf_prog_attach(const union bpf_attr *attr,
 			   enum bpf_prog_type ptype, struct bpf_prog *prog);
 int cgroup_bpf_prog_detach(const union bpf_attr *attr,
@@ -321,6 +330,7 @@ static inline int bpf_percpu_cgroup_storage_update(struct bpf_map *map,
 #define BPF_CGROUP_RUN_PROG_UDP6_SENDMSG_LOCK(sk, uaddr, t_ctx) ({ 0; })
 #define BPF_CGROUP_RUN_PROG_SOCK_OPS(sock_ops) ({ 0; })
 #define BPF_CGROUP_RUN_PROG_DEVICE_CGROUP(type,major,minor,access) ({ 0; })
+#define BPF_CGROUP_RUN_PROG_FILE_FILTER(file) ({ 0; })
 
 #define for_each_cgroup_storage_type(stype) for (; false; )
 
include/linux/bpf_types.h +1 -0
index 5432f4c9f50e..f182b2e37b94 100644
@@ -33,6 +33,7 @@ BPF_PROG_TYPE(BPF_PROG_TYPE_LIRC_MODE2, lirc_mode2)
 #ifdef CONFIG_INET
 BPF_PROG_TYPE(BPF_PROG_TYPE_SK_REUSEPORT, sk_reuseport)
 #endif
+BPF_PROG_TYPE(BPF_PROG_TYPE_FILE_FILTER, file_filter)
 
 BPF_MAP_TYPE(BPF_MAP_TYPE_ARRAY, array_map_ops)
 BPF_MAP_TYPE(BPF_MAP_TYPE_PERCPU_ARRAY, percpu_array_map_ops)
include/uapi/linux/bpf.h +27 -1
index f9187b41dff6..c0df8dd99edc 100644
@@ -154,6 +154,7 @@ enum bpf_prog_type {
 	BPF_PROG_TYPE_LIRC_MODE2,
 	BPF_PROG_TYPE_SK_REUSEPORT,
 	BPF_PROG_TYPE_FLOW_DISSECTOR,
+	BPF_PROG_TYPE_FILE_FILTER,
 };
 
 enum bpf_attach_type {
@@ -175,6 +176,7 @@ enum bpf_attach_type {
 	BPF_CGROUP_UDP6_SENDMSG,
 	BPF_LIRC_MODE2,
 	BPF_FLOW_DISSECTOR,
+	BPF_CGROUP_FILE_OPEN,
 	__MAX_BPF_ATTACH_TYPE
 };
 
@@ -2215,6 +2217,18 @@ union bpf_attr {
  *		pointer that was returned from bpf_sk_lookup_xxx\ ().
  *	Return
  *		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_get_file_path(struct bpf_file_info *file, char *buf, u32 size_of_buf)
+ * 	Description
+ * 		Reconstruct the full path of *file* and store it into *buf* of
+ * 		*size_of_buf*. The *size_of_buf* must be strictly positive.
+ * 		On success, the helper makes sure that the *buf* is NUL-terminated.
+ * 		On failure, it is filled with string "(error)".
+ * 		This helper should only be used for debugging.
+ * 		'char *path' should never be used for permission checks.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
  */
 #define __BPF_FUNC_MAPPER(FN)		\
 	FN(unspec),			\
@@ -2303,7 +2317,8 @@ union bpf_attr {
 	FN(skb_ancestor_cgroup_id),	\
 	FN(sk_lookup_tcp),		\
 	FN(sk_lookup_udp),		\
-	FN(sk_release),
+	FN(sk_release),			\
+	FN(get_file_path),
 
 /* integer value in 'imm' field of BPF_CALL instruction selects which helper
  * function eBPF program intends to call
@@ -2896,4 +2911,15 @@ struct bpf_flow_keys {
 	};
 };
 
+struct bpf_file_info {
+	__u64 inode;
+	__u32 dev_major;
+	__u32 dev_minor;
+	__u32 fs_magic;
+	__u32 mnt_id;
+	__u32 nlink;
+	__u32 mode;	/* file mode S_ISDIR, S_ISLNK, 0755, etc */
+	__u32 flags;	/* open flags O_RDWR, O_CREAT, etc */
+};
+
 #endif /* _UAPI__LINUX_BPF_H__ */
kernel/bpf/cgroup.c +171 -0

180 lines not on this page · raw message

kernel/bpf/syscall.c +7 -0
index 5742df21598c..7b0ffb8d7063 100644
@@ -1630,6 +1630,9 @@ static int bpf_prog_attach(const union bpf_attr *attr)
 	case BPF_FLOW_DISSECTOR:
 		ptype = BPF_PROG_TYPE_FLOW_DISSECTOR;
 		break;
+	case BPF_CGROUP_FILE_OPEN:
+		ptype = BPF_PROG_TYPE_FILE_FILTER;
+		break;
 	default:
 		return -EINVAL;
 	}
@@ -1699,6 +1702,9 @@ static int bpf_prog_detach(const union bpf_attr *attr)
 	case BPF_CGROUP_DEVICE:
 		ptype = BPF_PROG_TYPE_CGROUP_DEVICE;
 		break;
+	case BPF_CGROUP_FILE_OPEN:
+		ptype = BPF_PROG_TYPE_FILE_FILTER;
+		break;
 	case BPF_SK_MSG_VERDICT:
 		return sockmap_get_from_fd(attr, BPF_PROG_TYPE_SK_MSG, NULL);
 	case BPF_SK_SKB_STREAM_PARSER:
@@ -1741,6 +1747,7 @@ static int bpf_prog_query(const union bpf_attr *attr,
 	case BPF_CGROUP_UDP6_SENDMSG:
 	case BPF_CGROUP_SOCK_OPS:
 	case BPF_CGROUP_DEVICE:
+	case BPF_CGROUP_FILE_OPEN:
 		break;
 	case BPF_LIRC_MODE2:
 		return lirc_prog_query(attr, uattr);
Signature · 1 line
2.17.1
#2 lore raw

Re: [PATCH bpf-next 1/6] bpf: introduce BPF_PROG_TYPE_FILE_FILTER

Quoted 12 lines · Alexei Starovoitov

Similar to networking sandboxing programs and cgroup-v2 based hooks (BPF_CGROUP_INET_[INGRESS|EGRESS,] BPF_CGROUP_INET[4|6]_[BIND|CONNECT], etc) introduce basic per-container sandboxing for file access via
new BPF_PROG_TYPE_FILE_FILTER program type that attaches after security_file_open() LSM hook and works as additional file_open filter. The new cgroup bpf hook is called BPF_CGROUP_FILE_OPEN.

Just like other cgroup-bpf programs new BPF_PROG_TYPE_FILE_FILTER type is only available to root.

This program type has access to single argument 'struct bpf_file_info' that contains standard sys_stat fields:

Quoted 10 lines
struct bpf_file_info {
        __u64 inode;
        __u32 dev_major;
        __u32 dev_minor;
        __u32 fs_magic;
        __u32 mnt_id;
        __u32 nlink;
        __u32 mode;     /* file mode S_ISDIR, S_ISLNK, 0755, etc */
        __u32 flags;    /* open flags O_RDWR, O_CREAT, etc */
};

It's probably nice to have file uid/gid as well.

Thanks!

#3 lore raw

Re: [PATCH bpf-next 1/6] bpf: introduce BPF_PROG_TYPE_FILE_FILTER

Quoted 25 lines · Roman Gushchin
Quoted 12 lines · Alexei Starovoitov

Similar to networking sandboxing programs and cgroup-v2 based hooks (BPF_CGROUP_INET_[INGRESS|EGRESS,] BPF_CGROUP_INET[4|6]_[BIND|CONNECT], etc) introduce basic per-container sandboxing for file access via
new BPF_PROG_TYPE_FILE_FILTER program type that attaches after security_file_open() LSM hook and works as additional file_open filter. The new cgroup bpf hook is called BPF_CGROUP_FILE_OPEN.

Just like other cgroup-bpf programs new BPF_PROG_TYPE_FILE_FILTER type is only available to root.

This program type has access to single argument 'struct bpf_file_info' that contains standard sys_stat fields:

Quoted 10 lines
struct bpf_file_info {
        __u64 inode;
        __u32 dev_major;
        __u32 dev_minor;
        __u32 fs_magic;
        __u32 mnt_id;
        __u32 nlink;
        __u32 mode;     /* file mode S_ISDIR, S_ISLNK, 0755, etc */
        __u32 flags;    /* open flags O_RDWR, O_CREAT, etc */
};
Quoted 1 line

It's probably nice to have file uid/gid as well.

And an indication of which mount namespace we're looking at.

--Andy

#4 lore raw

Re: [PATCH bpf-next 1/6] bpf: introduce BPF_PROG_TYPE_FILE_FILTER

Quoted 26 lines · Andy Lutomirski
Quoted 25 lines · Roman Gushchin
Quoted 22 lines · Alexei Starovoitov

Similar to networking sandboxing programs and cgroup-v2 based hooks (BPF_CGROUP_INET_[INGRESS|EGRESS,] BPF_CGROUP_INET[4|6]_[BIND|CONNECT], etc) introduce basic per-container sandboxing for file access via
new BPF_PROG_TYPE_FILE_FILTER program type that attaches after security_file_open() LSM hook and works as additional file_open filter. The new cgroup bpf hook is called BPF_CGROUP_FILE_OPEN.

Just like other cgroup-bpf programs new BPF_PROG_TYPE_FILE_FILTER type is only available to root.

This program type has access to single argument 'struct bpf_file_info' that contains standard sys_stat fields:

struct bpf_file_info {
        __u64 inode;
        __u32 dev_major;
        __u32 dev_minor;
        __u32 fs_magic;
        __u32 mnt_id;
        __u32 nlink;
        __u32 mode;     /* file mode S_ISDIR, S_ISLNK, 0755, etc */
        __u32 flags;    /* open flags O_RDWR, O_CREAT, etc */
};
Quoted 3 lines
Quoted 1 line

It's probably nice to have file uid/gid as well.

And an indication of which mount namespace we're looking at.

Both certainly can be added in the future without breaking progs.
I didn't want to add too much all at once.
For file uid/gid I prototyped
bpf_get_statx(struct bpf_file_info *file, int flags, int mask, struct statx *sx, int size); helper that calls normal statx underneath.
But it's not fast, since sizeof(struct statx) == 256 and it has to be fully inited by the helper or by the bpf prog (since bpf doesn't allow uninited memory anywhere). Then I thought about going back to older sys_stat helper (without x), since structs are smaller, but that didn't look as good either. So I've decied to table it for now and get the basic support via 'struct bpf_file_info' first.
Then extend it later via new fields and new helpers.

maintainer
#5 lore raw

Re: [PATCH bpf-next 1/6] bpf: introduce BPF_PROG_TYPE_FILE_FILTER

In reply to Alexei Starovoitov

kernel/bpf/cgroup.c line 18

Quoted 5 lines · Alexei Starovoitov
kernel/bpf/cgroup.c +1 -0
@@ -15,6 +15,7 @@
 #include <linux/bpf.h>
 #include <linux/bpf-cgroup.h>
 #include <net/sock.h>
+#include <../fs/mount.h>

No.

kernel/bpf/cgroup.c const struct bpf_verifier_ops cg_dev_verifier_ops = { · line 867

Quoted 4 lines
kernel/bpf/cgroup.c +4 -0
+	struct file *file = NULL;
+	struct inode *inode;
+	struct super_block *sb;
+	struct mount *mnt;

Fuck, no.

kernel/bpf/cgroup.c const struct bpf_verifier_ops cg_dev_verifier_ops = { · line 912

Quoted 4 lines
kernel/bpf/cgroup.c +4 -0
+	case offsetof(struct bpf_file_info, mnt_id):
+		/* dst = real_mount(file->f_path.mnt)->mnt_id */
+		mnt = real_mount(LD_1(file->f_path.mnt));
+		LD_n(mnt->mnt_id);

NAK. Anything in struct mount is private to just a couple of files in fs/*.c. Don't do that. And keep in mind that internal details can and will be changed at zero notice, so be careful with adding such stuff.

Another problem is your direct poking in ->i_ino. It's not something directly exposed to userland at the moment and it should not become such. Filesystem has every right to have ->getattr() set ->ino (== ->st_ino value) in whichever way it likes; the same goes for ->dev.

#6 lore raw

Re: [PATCH bpf-next 1/6] bpf: introduce BPF_PROG_TYPE_FILE_FILTER

Quoted 9 lines · Al Viro

kernel/bpf/cgroup.c line 18

Quoted 5 lines · Alexei Starovoitov
kernel/bpf/cgroup.c +1 -0
@@ -15,6 +15,7 @@
 #include <linux/bpf.h>
 #include <linux/bpf-cgroup.h>
 #include <net/sock.h>
+#include <../fs/mount.h>

No.

I've considered splitting cgroup_file_filter_ctx_access() into separate .c inside fs/ directory, but it felt odd to move just that function whereas the rest of the bpf bits are in kernel/bpf/ only to avoid doing this "../fs/" hack.
How about calling this new file fs/bpf_file_filter.c ?

Quoted 7 lines

kernel/bpf/cgroup.c const struct bpf_verifier_ops cg_dev_verifier_ops = { · line 867

Quoted 4 lines
kernel/bpf/cgroup.c +4 -0
+	struct file *file = NULL;
+	struct inode *inode;
+	struct super_block *sb;
+	struct mount *mnt;

Fuck, no.

Quoted 9 lines

kernel/bpf/cgroup.c const struct bpf_verifier_ops cg_dev_verifier_ops = { · line 912

Quoted 4 lines
kernel/bpf/cgroup.c +4 -0
+	case offsetof(struct bpf_file_info, mnt_id):
+		/* dst = real_mount(file->f_path.mnt)->mnt_id */
+		mnt = real_mount(LD_1(file->f_path.mnt));
+		LD_n(mnt->mnt_id);

NAK. Anything in struct mount is private to just a couple of files in fs/*.c. Don't do that. And keep in mind that internal details can and will be changed at zero notice, so be careful with adding such stuff.

yes. The internal details of file and inode structs can and will change. Just like all the sk_buff internals that are exposed to bpf
via the same context rewriting mechanism.
See commit 9bac3d6d548e ("bpf: allow extended BPF programs access skb fields") that exposed first 4 fields out of sk_buff to bpf progs via 'struct __sk_buff'.
Other fields were exposed later. Some of them are not even from sk_buff. For networking programs 'struct __sk_buff' is the context.
For this new file_filter programs 'struct bpf_file_info' is the context. That's the only thing bpf side can see.

Quoted 3 lines

Another problem is your direct poking in ->i_ino. It's not something directly exposed to userland at the moment and it should not become such.

The patch is not making i_ino directly exposed.
Only 'struct bpf_file_info' is exposed to user space / bpf programs.

In 'struct bpf_file_info' the field is called '__u64 inode'. That is the abi.
The kernel side stays with 'unsigned long i_ino;' field. Its size is different on 32/64 architectures, but to bpf progs it's always seen as '__u64 inode'.
If in the future the kernel decides to change 'i_ino' to be u64, nothing will change on bpf side.

Quoted 2 lines

Filesystem has every right to have ->getattr() set ->ino (== ->st_ino value) in whichever way it likes;

Essentially what cgroup_file_filter_ctx_access() is doing is the same as generic_fillattr() + cp_statx() work, but via on the fly rewriting of bpf instructions during loading of bpf program.

See my other reply to Andy where I argued that certain fields like uid/gid probably don't make sense to expose to bpf via ctx rewriter mechanism and instead they can be done via new bpf_get_file_statx() helper.
That's future work.

Quoted 2 lines

the same
goes for ->dev.

Notice how single kernel field file->f_inode->i_sb->s_dev is exposed to bpf via two fields dev_major and dev_minor inside 'struct bpf_file_info'.
For dev_minor the ctx rewriting is done as:
+ case offsetof(struct bpf_file_info, dev_minor):

+               /* dst = file->f_inode->i_sb->s_dev */
+               inode = LD_1(file->f_inode);
+               sb = LD_n(inode->i_sb);
+               LD_n(sb->s_dev);
+               *insn++ = BPF_ALU32_IMM(BPF_AND, si->dst_reg, MINORMASK);
+               break;

If the last AND instruction was not there, the whole 's_dev' field would have been exposed to bpf side and that would be questionable design choice, since s_dev has kernel internal encoding of major/minor. Instead the AND instruction is masking minor bits.
So above ctx rewriting for 'dev_minor' field is equivalent to line
stat->dev = inode->i_sb->s_dev;
from generic_fillattr()
plus another line
tmp.stx_dev_minor = MINOR(stat->dev);
from cp_statx()

This way the kernel internal field 's_dev' is split into two dev_major/dev_minor bpf visible fields.

The end result is that no new kernel information is exposed. The same information is seen via statx.

#7 lore raw

Re: [PATCH bpf-next 1/6] bpf: introduce BPF_PROG_TYPE_FILE_FILTER

Quoted 9 lines · Alexei Starovoitov
Quoted 3 lines · Al Viro

Another problem is your direct poking in ->i_ino. It's not something directly exposed to userland at the moment and it should not become such.

The patch is not making i_ino directly exposed.
Only 'struct bpf_file_info' is exposed to user space / bpf programs.

I think Al is saying that the valie of i_ino is not something that user code is permitted to know regardless of how you format it because it may or may not actually match the value returned by stat(). Another way of saying that is that your patch is digging into an internal data structure and is doing it wrong.

--Andy

#8 lore raw

Re: [PATCH bpf-next 1/6] bpf: introduce BPF_PROG_TYPE_FILE_FILTER

Quoted 9 lines · Andy Lutomirski
Quoted 7 lines · Alexei Starovoitov
Quoted 3 lines · Al Viro

Another problem is your direct poking in ->i_ino. It's not something directly exposed to userland at the moment and it should not become such.

Quoted 8 lines
Quoted 2 lines

The patch is not making i_ino directly exposed.
Only 'struct bpf_file_info' is exposed to user space / bpf programs.

I think Al is saying that the valie of i_ino is not something that user code is permitted to know regardless of how you format it because it may or may not actually match the value returned by stat(). Another way of saying that is that your patch is digging into an internal data structure and is doing it wrong.

several fs implementation I've looked at just do generic_fillattr() for these fields. Are you saying some FS don't use inode->i_ino at all? And it's bogus, hence shouldn't be read?

maintainer NAK
#9 lore raw

Re: [PATCH bpf-next 1/6] bpf: introduce BPF_PROG_TYPE_FILE_FILTER

Quoted 19 lines · Alexei Starovoitov
Quoted 9 lines · Andy Lutomirski
Quoted 7 lines · Alexei Starovoitov
Quoted 3 lines · Al Viro

Another problem is your direct poking in ->i_ino. It's not something directly exposed to userland at the moment and it should not become such.

Quoted 8 lines
Quoted 2 lines

The patch is not making i_ino directly exposed.
Only 'struct bpf_file_info' is exposed to user space / bpf programs.

I think Al is saying that the valie of i_ino is not something that user code is permitted to know regardless of how you format it because it may or may not actually match the value returned by stat(). Another way of saying that is that your patch is digging into an internal data structure and is doing it wrong.

Quoted 3 lines

several fs implementation I've looked at just do generic_fillattr() for these fields. Are you saying some FS don't use inode->i_ino at all? And it's bogus, hence shouldn't be read?

Bloody wonderful... "Several instances use a library function and do not override that part of its results; ergo, let's assume that all of them do the same".

generic_fillattr() is a library function. In a lot of cases this is all ->getattr() instance needs to do. And yes, use of ->i_dev and ->i_ino to intialize ->st_dev and ->st_ino happens to be the default. However, this is entirely up to the filesystem in question. These fields are fs-private; whether to use them for stat(2) (or anything userland-visible, really) or to calculate some other value is up to individual filesystem.

FWIW, finding which instances do that is as simple as
grep -n '[-]>[[:space:]]*ino[[:space:]]*=' git grep -l -w generic_fillattr on the plausible theory that ->getattr() instances will be using that helper at least for some of the fields. Discarding fs/stat.c, where generic_fillattr() itself lives, we are left with
fs/ceph/inode.c:558: if (realm->ino == ci->i_vino.ino)
fs/ceph/inode.c:2268: stat->ino = ceph_translate_ino(inode->i_sb, inode->i_ino);
fs/cifs/inode.c:2067: stat->ino = CIFS_I(inode)->uniqueid;
fs/fat/file.c:410: stat->ino = fat_i_pos_read(MSDOS_SB(inode->i_sb), inode);
fs/fuse/dir.c:866: stat->ino = attr->ino;
fs/fuse/dir.c:954: stat->ino = fi->orig_ino;
fs/nfs/inode.c:841: stat->ino = nfs_compat_user_ino64(NFS_FILEID(inode));
Trivial examination shows that all matches except the first one *are* in ->getattr() instances of the filesystems in question or are called from such.

And no, it's not that each of those filesystems does not use inode->i_ino at all. There's a bunch of library helpers in fs/*.c that happen to use the value filesystem has seen fit to store there. Whether to use those helpers or not, what to store in that field, etc. is, again, entirely up to the filesystem in question. generic_fillattr() is one of those, that's all there is to it.

That's precisely why I really do not like the idea of hooks poking in the internals of kernel data structures. Especially since not even "it's not visible outside of a subsystem-internal header" appears to slow you down.

The same goes for tracepoints, etc. - turning random details of implementation into a carved in stone ABI is actively harmful.

NAK.

PS: If anything, visibility to hooks should be opt-in. Sure, we can start actively hiding the things, but that's a winless arms race - even if we bloody went and encrypted the private stuff, you'd still be able to pull decryption key from where it would be accessed by legitimate users, etc. Нахуй нам это надо?

#10 lore raw

Re: [PATCH bpf-next 1/6] bpf: introduce BPF_PROG_TYPE_FILE_FILTER

Quoted 6 lines · Al Viro

And no, it's not that each of those filesystems does not use inode->i_ino at all. There's a bunch of library helpers in fs/*.c that happen to use the value filesystem has seen fit to store there. Whether to use those helpers or not, what to store in that field, etc. is, again, entirely up to the filesystem in question. generic_fillattr() is one of those, that's all there is to it.

makes sense.

Quoted 3 lines

That's precisely why I really do not like the idea of hooks poking in the internals of kernel data structures. Especially since not even "it's not visible outside of a subsystem-internal header" appears to slow you down.

that's why there is a code review to get the patches in shape that don't expose kernel internals by _accident_.

Quoted 2 lines

The same goes for tracepoints, etc. - turning random details of implementation into a carved in stone ABI is actively harmful.

we're not talking about tracepoints here.

Quoted 1 line

PS: If anything, visibility to hooks should be opt-in.

not sure how do you expect 'opt-in' to be imlemented.
As far as exposing inode and dev I agree that was a wrong approach. Going to switch to struct file_handle instead.
Doing statx for every file_open is probably too slow for practical use.

Quoted 4 lines

Sure, we can start actively hiding
the things, but that's a winless arms race - even if we bloody went and encrypted the private stuff, you'd still be able to pull decryption key from where it would be accessed by legitimate users, etc. Нахуй нам это надо?

why do this ?
the use cases are explained in commit log.

Back to internals exposure.
Do you see an issue in the following:

struct bpf_file_info {
        __u32 fs_magic; // file->f_inode->i_sb->s_magic
        __u32 mnt_id;   // real_mount(file->f_path.mnt)->mnt_id
        __u32 nlink;    // file->f_inode->i_nlink
        __u32 mode;     // file->f_inode->i_mode
        __u32 flags;    // file->f_flags
};

and separate helper that returns struct file_handle ? and ctx rewriting moved to fs/bpf_file_filter.c ?

#11 lore raw

Re: [PATCH bpf-next 1/6] bpf: introduce BPF_PROG_TYPE_FILE_FILTER

In reply to Alexei Starovoitov

+cc kernel-hardening because this is related to sandboxing +cc Mickaël Salaün because this looks related to his Landlock proposal

Quoted 6 lines · Alexei Starovoitov

Similar to networking sandboxing programs and cgroup-v2 based hooks (BPF_CGROUP_INET_[INGRESS|EGRESS,] BPF_CGROUP_INET[4|6]_[BIND|CONNECT], etc) introduce basic per-container sandboxing for file access via
new BPF_PROG_TYPE_FILE_FILTER program type that attaches after security_file_open() LSM hook and works as additional file_open filter. The new cgroup bpf hook is called BPF_CGROUP_FILE_OPEN.

Why do_dentry_open() specifically, and nothing else? If you want to filter open, wouldn't you also want to filter a bunch of other filesystem operations with LSM hooks, like rename, unlink, truncate and so on? Landlock benefits there from re-using the existing security hooks.

Quoted 21 lines

Just like other cgroup-bpf programs new BPF_PROG_TYPE_FILE_FILTER type is only available to root.

This program type has access to single argument 'struct bpf_file_info' that contains standard sys_stat fields:

struct bpf_file_info {
        __u64 inode;
        __u32 dev_major;
        __u32 dev_minor;
        __u32 fs_magic;
        __u32 mnt_id;
        __u32 nlink;
        __u32 mode;     /* file mode S_ISDIR, S_ISLNK, 0755, etc */
        __u32 flags;    /* open flags O_RDWR, O_CREAT, etc */
};

Other file attributes can be added in the future to the end of this struct without breaking bpf programs.

For debugging introduce bpf_get_file_path() helper that returns NUL-terminated full path of the file. It should never be used for sandboxing.

Quoted 6 lines

Use cases:
- disallow certain FS types within containers (fs_magic == CGROUP2_SUPER_MAGIC)
- restrict permissions in particular mount (mnt_id == X && (flags & O_RDWR))
- disallow access to hard linked sensitive files (nlink > 1 && mode == 0700)
- disallow access to world writeable files (mode == 0..7)
- disallow access to given set of files (dev_major == X && dev_minor == Y && inode == Z)

That last one sounds weird. It doesn't work if you want to ban access to a whole directory at once. And in general, highly specific blocklists make me nervous, because if you add anything new and forget to put it on the list, you have a problem.

Quoted 119 lines
---
 include/linux/bpf-cgroup.h |  10 +++
 include/linux/bpf_types.h  |   1 +
 include/uapi/linux/bpf.h   |  28 +++++-
 kernel/bpf/cgroup.c        | 171 +++++++++++++++++++++++++++++++++++++
 kernel/bpf/syscall.c       |   7 ++
 5 files changed, 216 insertions(+), 1 deletion(-)
include/linux/bpf-cgroup.h +10 -0
index 588dd5f0bd85..766f0223c222 100644
@@ -109,6 +109,8 @@ int __cgroup_bpf_run_filter_sock_ops(struct sock *sk,
 int __cgroup_bpf_check_dev_permission(short dev_type, u32 major, u32 minor,
                                      short access, enum bpf_attach_type type);
 
+int __cgroup_bpf_file_filter(struct file *file, enum bpf_attach_type type);
+
 static inline enum bpf_cgroup_storage_type cgroup_storage_type(
        struct bpf_map *map)
 {
@@ -253,6 +255,13 @@ int bpf_percpu_cgroup_storage_update(struct bpf_map *map, void *key,
                                                                              \
        __ret;                                                                \
 })
+#define BPF_CGROUP_RUN_PROG_FILE_FILTER(file)                               \
+({                                                                           \
+       int __ret = 0;                                                        \
+       if (cgroup_bpf_enabled)                                               \
+               __ret = __cgroup_bpf_file_filter(file, BPF_CGROUP_FILE_OPEN); \
+       __ret;                                                                \
+})
 int cgroup_bpf_prog_attach(const union bpf_attr *attr,
                           enum bpf_prog_type ptype, struct bpf_prog *prog);
 int cgroup_bpf_prog_detach(const union bpf_attr *attr,
@@ -321,6 +330,7 @@ static inline int bpf_percpu_cgroup_storage_update(struct bpf_map *map,
 #define BPF_CGROUP_RUN_PROG_UDP6_SENDMSG_LOCK(sk, uaddr, t_ctx) ({ 0; })
 #define BPF_CGROUP_RUN_PROG_SOCK_OPS(sock_ops) ({ 0; })
 #define BPF_CGROUP_RUN_PROG_DEVICE_CGROUP(type,major,minor,access) ({ 0; })
+#define BPF_CGROUP_RUN_PROG_FILE_FILTER(file) ({ 0; })
 
 #define for_each_cgroup_storage_type(stype) for (; false; )
 
include/linux/bpf_types.h +1 -0
index 5432f4c9f50e..f182b2e37b94 100644
@@ -33,6 +33,7 @@ BPF_PROG_TYPE(BPF_PROG_TYPE_LIRC_MODE2, lirc_mode2)
 #ifdef CONFIG_INET
 BPF_PROG_TYPE(BPF_PROG_TYPE_SK_REUSEPORT, sk_reuseport)
 #endif
+BPF_PROG_TYPE(BPF_PROG_TYPE_FILE_FILTER, file_filter)
 
 BPF_MAP_TYPE(BPF_MAP_TYPE_ARRAY, array_map_ops)
 BPF_MAP_TYPE(BPF_MAP_TYPE_PERCPU_ARRAY, percpu_array_map_ops)
include/uapi/linux/bpf.h +26 -1
index f9187b41dff6..c0df8dd99edc 100644
@@ -154,6 +154,7 @@ enum bpf_prog_type {
        BPF_PROG_TYPE_LIRC_MODE2,
        BPF_PROG_TYPE_SK_REUSEPORT,
        BPF_PROG_TYPE_FLOW_DISSECTOR,
+       BPF_PROG_TYPE_FILE_FILTER,
 };
 
 enum bpf_attach_type {
@@ -175,6 +176,7 @@ enum bpf_attach_type {
        BPF_CGROUP_UDP6_SENDMSG,
        BPF_LIRC_MODE2,
        BPF_FLOW_DISSECTOR,
+       BPF_CGROUP_FILE_OPEN,
        __MAX_BPF_ATTACH_TYPE
 };
 
@@ -2215,6 +2217,18 @@ union bpf_attr {
  *             pointer that was returned from bpf_sk_lookup_xxx\ ().
  *     Return
  *             0 on success, or a negative error in case of failure.
+ *
+ * int bpf_get_file_path(struct bpf_file_info *file, char *buf, u32 size_of_buf)
+ *     Description
+ *             Reconstruct the full path of *file* and store it into *buf* of
+ *             *size_of_buf*. The *size_of_buf* must be strictly positive.
+ *             On success, the helper makes sure that the *buf* is NUL-terminated.
+ *             On failure, it is filled with string "(error)".
+ *             This helper should only be used for debugging.
+ *             'char *path' should never be used for permission checks.
+ *     Return
+ *             0 on success, or a negative error in case of failure.
+ *
  */
 #define __BPF_FUNC_MAPPER(FN)          \
        FN(unspec),                     \
@@ -2303,7 +2317,8 @@ union bpf_attr {
        FN(skb_ancestor_cgroup_id),     \
        FN(sk_lookup_tcp),              \
        FN(sk_lookup_udp),              \
-       FN(sk_release),
+       FN(sk_release),                 \
+       FN(get_file_path),
 
 /* integer value in 'imm' field of BPF_CALL instruction selects which helper
  * function eBPF program intends to call
@@ -2896,4 +2911,15 @@ struct bpf_flow_keys {
        };
 };
 
+struct bpf_file_info {
+       __u64 inode;
+       __u32 dev_major;
+       __u32 dev_minor;
+       __u32 fs_magic;
+       __u32 mnt_id;
+       __u32 nlink;
+       __u32 mode;     /* file mode S_ISDIR, S_ISLNK, 0755, etc */
+       __u32 flags;    /* open flags O_RDWR, O_CREAT, etc */
+};

[...]

#12 lore raw

Re: [PATCH bpf-next 1/6] bpf: introduce BPF_PROG_TYPE_FILE_FILTER

Review

Quoted 2 lines · Jann Horn
(quoted diff) +2 -0
+cc kernel-hardening because this is related to sandboxing
+cc Mickaël Salaün because this looks related to his Landlock proposal

It may seem that this work overlaps with landlock, but the goals are different. Landlock is LSM based to act as _security_ framework with end goal being available to unpriv users.
While this cgroup-bpf hook is expected to stay root only,
since we're trying to restrict what containers can do in a trusted environment. 'sandboxing' is overloaded word.
Sandboxing for security and sandboxing of trusted root are different.

Quoted 8 lines · Alexei Starovoitov
Quoted 6 lines

Similar to networking sandboxing programs and cgroup-v2 based hooks (BPF_CGROUP_INET_[INGRESS|EGRESS,] BPF_CGROUP_INET[4|6]_[BIND|CONNECT], etc) introduce basic per-container sandboxing for file access via
new BPF_PROG_TYPE_FILE_FILTER program type that attaches after security_file_open() LSM hook and works as additional file_open filter. The new cgroup bpf hook is called BPF_CGROUP_FILE_OPEN.

Quoted 5 lines

Why do_dentry_open() specifically, and nothing else? If you want to filter open, wouldn't you also want to filter a bunch of other filesystem operations with LSM hooks, like rename, unlink, truncate and so on? Landlock benefits there from re-using the existing security hooks.

It may make sense to extend in the future, but we don't have clear user cases for rename/unlink/truncate at this point.

As you can see the amount of pushback even for basic file access is high. Hence I don't think the landlock can be upstreamed in the current form, since it touches VFS layer a lot more than this patch.
It's intrusive to LSM, and adds new concepts to BPF as well. This work fits into existing BPF machinery and minimally intrusive to VFS. I hope we can find a common ground with Al regarding what file access primitives are exposed to BPF side.
Once we agree on that the landlock can piggy back on this work and extend it to all file-based LSM hooks.
The first step for everyone interested in bpf-based 'sandboxing' is to figure out VFS<->BPF interface.
If you or Mickael have suggestions on what bpf progs should and should not see at these hooks, it's a good time to discuss.
I believe the fields proposed are the obvious minimum.

Quoted 28 lines
Quoted 21 lines

Just like other cgroup-bpf programs new BPF_PROG_TYPE_FILE_FILTER type is only available to root.

This program type has access to single argument 'struct bpf_file_info' that contains standard sys_stat fields:

struct bpf_file_info {
        __u64 inode;
        __u32 dev_major;
        __u32 dev_minor;
        __u32 fs_magic;
        __u32 mnt_id;
        __u32 nlink;
        __u32 mode;     /* file mode S_ISDIR, S_ISLNK, 0755, etc */
        __u32 flags;    /* open flags O_RDWR, O_CREAT, etc */
};

Other file attributes can be added in the future to the end of this struct without breaking bpf programs.

For debugging introduce bpf_get_file_path() helper that returns NUL-terminated full path of the file. It should never be used for sandboxing.

Quoted 6 lines

Use cases:
- disallow certain FS types within containers (fs_magic == CGROUP2_SUPER_MAGIC)
- restrict permissions in particular mount (mnt_id == X && (flags & O_RDWR))
- disallow access to hard linked sensitive files (nlink > 1 && mode == 0700)
- disallow access to world writeable files (mode == 0..7)
- disallow access to given set of files (dev_major == X && dev_minor == Y && inode == Z)

Quoted 4 lines

That last one sounds weird. It doesn't work if you want to ban access to a whole directory at once. And in general, highly specific blocklists make me nervous, because if you add anything new and forget to put it on the list, you have a problem.

In the upcoming V2 of the patches the direct exposure to dev and inode will be removed. And instead the opaque 'struct file_handle' will be available to bpf progs. The use case is indeed to restrict access to specific blacklist of files. The user space will collect the set of files via sys_name_to_handle_at(), then store the fhandles in bpf map, and bpf prog will consult the map
to deny the access.
It's not a replacement for ACLs, directory permissions, etc. The use case is to prevent trusted containers messing up the environment.
The continuous integration system needs to run some containers (and tests inside them) with root privs. When these jobs mess up the system the subsequent jobs may incorrectly fail. We believe that this cgroup based container enforcement will solve this use case and similar other use cases when containers are trusted, but could be buggy when it comes to file access.

To recap what I'm implementing in V2:
1.

struct bpf_file_info {
        __u32 fs_magic; // file->f_inode->i_sb->s_magic
        __u32 mnt_id;   // real_mount(file->f_path.mnt)->mnt_id
        __u32 nlink;    // file->f_inode->i_nlink
        __u32 mode;     // file->f_inode->i_mode
        __u32 flags;    // file->f_flags
};

I double checked what VFS layer does with above fields and I think there is no additional user space exposure will be made when such fields are seen by bpf progs.
But since I'm not a VFS expert, I'd like Al to confirm.

2.
bpf_get_file_handle(struct bpf_file_info *ctx, struct file_handle *fh, int fh_size); helper that bpf prog will use to obtain fh of the file about to be open.

3.
bpf_get_file_statx(struct bpf_file_info *ctx, struct statx *sx, int size, int flags); Though struct statx is 256 bytes, and the helper would have to touch all bytes I couldn't figure out the faster way to get to inode/dev/uid of the given file that will work on all underlying FSes.

Thoughts?

#13 lore raw

Re: [PATCH bpf-next 1/6] bpf: introduce BPF_PROG_TYPE_FILE_FILTER

Quoted 3 lines · Alexei Starovoitov

Review

Quoted 2 lines · Jann Horn
(quoted diff) +2 -0
+cc kernel-hardening because this is related to sandboxing
+cc Mickaël Salaün because this looks related to his Landlock proposal

Thanks for the CC. Because this patch series is an access-control mechanism, it will undoubtedly interest LSM folks as well.

Here is the full thread:
https://lore.kernel.org/lkml/[email protected]/

Quoted 4 lines

It may seem that this work overlaps with landlock, but the goals are different. Landlock is LSM based to act as _security_ framework with end goal being available to unpriv users.

Right, one of the end goal of Landlock is to make most of its access-control features available to unprivileged processes. However, some of them may require higher privileges, but we should minimize them as much as possible to not end up with the SUID-binary (or CAP_SYS_ADMIN) syndrome.

Quoted 4 lines

While this cgroup-bpf hook is expected to stay root only,
since we're trying to restrict what containers can do in a trusted environment. 'sandboxing' is overloaded word.
Sandboxing for security and sandboxing of trusted root are different.

Sure we must treat them in a different manner but we must take care of which capability this sandboxing mechanism really need, and hopefully not root-like ones, especially for a container.

As a reminder, using cgroups to sandbox processes is definitely a use case for Landlock, as well as the process hierarchy one (seccomp-like). I implemented sandboxing with cgroups in a previous patch series, but put it aside for now to minimize the number of patches.

Quoted 9 lines
Quoted 9 lines · Alexei Starovoitov
Quoted 6 lines

Similar to networking sandboxing programs and cgroup-v2 based hooks (BPF_CGROUP_INET_[INGRESS|EGRESS,] BPF_CGROUP_INET[4|6]_[BIND|CONNECT], etc) introduce basic per-container sandboxing for file access via
new BPF_PROG_TYPE_FILE_FILTER program type that attaches after security_file_open() LSM hook and works as additional file_open filter. The new cgroup bpf hook is called BPF_CGROUP_FILE_OPEN.

Quoted 8 lines
Quoted 5 lines

Why do_dentry_open() specifically, and nothing else? If you want to filter open, wouldn't you also want to filter a bunch of other filesystem operations with LSM hooks, like rename, unlink, truncate and so on? Landlock benefits there from re-using the existing security hooks.

It may make sense to extend in the future, but we don't have clear user cases for rename/unlink/truncate at this point.

I guess the users who ask for the current features will come with new ones, which will move towards the same goal as Landlock.

Quoted 4 lines

As you can see the amount of pushback even for basic file access is high. Hence I don't think the landlock can be upstreamed in the current form, since it touches VFS layer a lot more than this patch.

That is not true for the main part. The only patch from Landlock touching the FS subsystem is to add a new LSM hook. This was NAKed by Al but I guess mostly because of misunderstanding. I'll send a new specific patch to the FS subsystem soon to properly explain the impact on path lookup and why it doesn't expose more that userspace have already access.

Regarding the upstreaming concern, if I remember correctly, the net/BPF and the LSM maintainers are (mostly ?) OK. The feedback I got (you included) was good.

I can definitely create a lighter version of Landlock with your use cases in mind. There is already all the necessary building blocks: the BPF (un-)privileged handling, the file checks (see the FS_PICK subtype) and the cgroups handling. I may even be easier to upstream (and answer Casey's request) to make the patch series smaller.

Quoted 6 lines

It's intrusive to LSM, and adds new concepts to BPF as well. This work fits into existing BPF machinery and minimally intrusive to VFS. I hope we can find a common ground with Al regarding what file access primitives are exposed to BPF side.
Once we agree on that the landlock can piggy back on this work and extend it to all file-based LSM hooks.

Can you explain your thought about piggy backing? If your patch is included, Landlock will still need to add new BPF prog types, and will still be an LSM (which is designed to handle access-control).

Quoted 5 lines

The first step for everyone interested in bpf-based 'sandboxing' is to figure out VFS<->BPF interface.
If you or Mickael have suggestions on what bpf progs should and should not see at these hooks, it's a good time to discuss.
I believe the fields proposed are the obvious minimum.

In a nutshell, Landlock rely on an inode map, which contains references to inodes. These references/handles can then be used to "compare" with a requested inode. This way, we can limit the checks to what is already available to the process which loaded the map. I suggest to not use these hardcoded fields but use a BFP helper with a file handle as argument and dedicated flags, as I did in a previous version of Landlock (which I planned to upstream as a new future feature).

Quoted 19 lines
Quoted 19 lines
Quoted 19 lines

Just like other cgroup-bpf programs new BPF_PROG_TYPE_FILE_FILTER type is only available to root.

This program type has access to single argument 'struct bpf_file_info' that contains standard sys_stat fields:

struct bpf_file_info {
        __u64 inode;
        __u32 dev_major;
        __u32 dev_minor;
        __u32 fs_magic;
        __u32 mnt_id;
        __u32 nlink;
        __u32 mode;     /* file mode S_ISDIR, S_ISLNK, 0755, etc */
        __u32 flags;    /* open flags O_RDWR, O_CREAT, etc */
};

Other file attributes can be added in the future to the end of this struct without breaking bpf programs.

Quoted 2 lines
Quoted 2 lines
Quoted 2 lines

For debugging introduce bpf_get_file_path() helper that returns NUL-terminated full path of the file. It should never be used for sandboxing.

Well, I'm convinced it *will* be used for sandboxing. Once a feature is available, it is too late to forbid users to use it the way they want, even if you warn them about security issues…

Quoted 7 lines
Quoted 7 lines
Quoted 7 lines

Use cases:
- disallow certain FS types within containers (fs_magic == CGROUP2_SUPER_MAGIC)
- restrict permissions in particular mount (mnt_id == X && (flags & O_RDWR))
- disallow access to hard linked sensitive files (nlink > 1 && mode == 0700)
- disallow access to world writeable files (mode == 0..7)
- disallow access to given set of files (dev_major == X && dev_minor == Y && inode == Z)

These use cases are interesting and should help to harden containers. They are definitely in the scope of what Landlock can do.

However, I would like to see which capabilities are needed to access these properties.

Quoted 29 lines
Quoted 5 lines

That last one sounds weird. It doesn't work if you want to ban access to a whole directory at once. And in general, highly specific blocklists make me nervous, because if you add anything new and forget to put it on the list, you have a problem.

In the upcoming V2 of the patches the direct exposure to dev and inode will be removed. And instead the opaque 'struct file_handle' will be available to bpf progs. The use case is indeed to restrict access to specific blacklist of files. The user space will collect the set of files via sys_name_to_handle_at(), then store the fhandles in bpf map, and bpf prog will consult the map
to deny the access.
It's not a replacement for ACLs, directory permissions, etc. The use case is to prevent trusted containers messing up the environment.
The continuous integration system needs to run some containers (and tests inside them) with root privs. When these jobs mess up the system the subsequent jobs may incorrectly fail. We believe that this cgroup based container enforcement will solve this use case and similar other use cases when containers are trusted, but could be buggy when it comes to file access.

To recap what I'm implementing in V2:
1.

struct bpf_file_info {
        __u32 fs_magic; // file->f_inode->i_sb->s_magic
        __u32 mnt_id;   // real_mount(file->f_path.mnt)->mnt_id
        __u32 nlink;    // file->f_inode->i_nlink
        __u32 mode;     // file->f_inode->i_mode
        __u32 flags;    // file->f_flags
};
Quoted 8 lines

I double checked what VFS layer does with above fields and I think there is no additional user space exposure will be made when such fields are seen by bpf progs.
But since I'm not a VFS expert, I'd like Al to confirm.

2.
bpf_get_file_handle(struct bpf_file_info *ctx, struct file_handle *fh, int fh_size); helper that bpf prog will use to obtain fh of the file about to be open.

This looks like Landlock. :)

Quoted 6 lines

3.
bpf_get_file_statx(struct bpf_file_info *ctx, struct statx *sx, int size, int flags); Though struct statx is 256 bytes, and the helper would have to touch all bytes I couldn't figure out the faster way to get to inode/dev/uid of the given file that will work on all underlying FSes.

This looks like a previous helper I implemented, but tried to avoid because of security issues (mostly for unpriv processes).

Quoted 4 lines

Thoughts?

author maintainer
#14 lore raw

PATCH 2/6 fs: wire in BPF_CGROUP_FILE_OPEN hook

In reply to Alexei Starovoitov

enable cgroup-bpf BPF_CGROUP_FILE_OPEN hook after security_file_open() LSM hook. Similarly to other cgroup-bpf hooks it's gated by static key 'cgroup_bpf_enabled' and has zero overhead until bpf prog is attached to that hook.

---
 fs/open.c | 4 ++++
 1 file changed, 4 insertions(+)
fs/open.c +4 -0
index 0285ce7dbd51..7e1170863f40 100644
@@ -759,6 +759,10 @@ static int do_dentry_open(struct file *f,
 	if (error)
 		goto cleanup_all;
 
+	error = BPF_CGROUP_RUN_PROG_FILE_FILTER(f);
+	if (error)
+		goto cleanup_all;
+
 	error = break_lease(locks_inode(f), f->f_flags);
 	if (error)
 		goto cleanup_all;
Signature · 1 line
2.17.1
author maintainer
#15 lore raw

PATCH 3/6 tools/bpf: sync uapi/bpf.h

In reply to Alexei Starovoitov

sync uapi/bpf.h from kernel into tools

---
 tools/include/uapi/linux/bpf.h | 28 +++++++++++++++++++++++++++-
 1 file changed, 27 insertions(+), 1 deletion(-)
tools/include/uapi/linux/bpf.h +27 -1
index f9187b41dff6..c0df8dd99edc 100644
@@ -154,6 +154,7 @@ enum bpf_prog_type {
 	BPF_PROG_TYPE_LIRC_MODE2,
 	BPF_PROG_TYPE_SK_REUSEPORT,
 	BPF_PROG_TYPE_FLOW_DISSECTOR,
+	BPF_PROG_TYPE_FILE_FILTER,
 };
 
 enum bpf_attach_type {
@@ -175,6 +176,7 @@ enum bpf_attach_type {
 	BPF_CGROUP_UDP6_SENDMSG,
 	BPF_LIRC_MODE2,
 	BPF_FLOW_DISSECTOR,
+	BPF_CGROUP_FILE_OPEN,
 	__MAX_BPF_ATTACH_TYPE
 };
 
@@ -2215,6 +2217,18 @@ union bpf_attr {
  *		pointer that was returned from bpf_sk_lookup_xxx\ ().
  *	Return
  *		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_get_file_path(struct bpf_file_info *file, char *buf, u32 size_of_buf)
+ * 	Description
+ * 		Reconstruct the full path of *file* and store it into *buf* of
+ * 		*size_of_buf*. The *size_of_buf* must be strictly positive.
+ * 		On success, the helper makes sure that the *buf* is NUL-terminated.
+ * 		On failure, it is filled with string "(error)".
+ * 		This helper should only be used for debugging.
+ * 		'char *path' should never be used for permission checks.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
  */
 #define __BPF_FUNC_MAPPER(FN)		\
 	FN(unspec),			\
@@ -2303,7 +2317,8 @@ union bpf_attr {
 	FN(skb_ancestor_cgroup_id),	\
 	FN(sk_lookup_tcp),		\
 	FN(sk_lookup_udp),		\
-	FN(sk_release),
+	FN(sk_release),			\
+	FN(get_file_path),
 
 /* integer value in 'imm' field of BPF_CALL instruction selects which helper
  * function eBPF program intends to call
@@ -2896,4 +2911,15 @@ struct bpf_flow_keys {
 	};
 };
 
+struct bpf_file_info {
+	__u64 inode;
+	__u32 dev_major;
+	__u32 dev_minor;
+	__u32 fs_magic;
+	__u32 mnt_id;
+	__u32 nlink;
+	__u32 mode;	/* file mode S_ISDIR, S_ISLNK, 0755, etc */
+	__u32 flags;	/* open flags O_RDWR, O_CREAT, etc */
+};
+
 #endif /* _UAPI__LINUX_BPF_H__ */
Signature · 1 line
2.17.1
author maintainer
#16 lore raw

PATCH 4/6 trace/bpf: allow %o modifier in bpf_trace_printk

In reply to Alexei Starovoitov

Allow %o modifier in bpf_trace_printk() helper

---
 kernel/trace/bpf_trace.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
kernel/trace/bpf_trace.c +1 -1
index 08fcfe440c63..b993efef1a4b 100644
@@ -233,7 +233,7 @@ BPF_CALL_5(bpf_trace_printk, char *, fmt, u32, fmt_size, u64, arg1,
 			i++;
 		}
 
-		if (fmt[i] != 'i' && fmt[i] != 'd' &&
+		if (fmt[i] != 'i' && fmt[i] != 'd' && fmt[i] != 'o' &&
 		    fmt[i] != 'u' && fmt[i] != 'x')
 			return -EINVAL;
 		fmt_cnt++;
Signature · 1 line
2.17.1
author maintainer
#17 lore raw

PATCH 5/6 libbpf: support BPF_CGROUP_FILE_OPEN in libbpf

In reply to Alexei Starovoitov

add support for BPF_CGROUP_FILE_OPEN in libbpf

---
 tools/lib/bpf/libbpf.c                    | 3 +++
 tools/testing/selftests/bpf/bpf_helpers.h | 2 ++
 2 files changed, 5 insertions(+)
tools/lib/bpf/libbpf.c +3 -0
index 9e68fd9fcfca..ceb06d4cb20a 100644
@@ -1503,6 +1503,7 @@ static bool bpf_prog_type__needs_kver(enum bpf_prog_type type)
 	case BPF_PROG_TYPE_LIRC_MODE2:
 	case BPF_PROG_TYPE_SK_REUSEPORT:
 	case BPF_PROG_TYPE_FLOW_DISSECTOR:
+	case BPF_PROG_TYPE_FILE_FILTER:
 		return false;
 	case BPF_PROG_TYPE_UNSPEC:
 	case BPF_PROG_TYPE_KPROBE:
@@ -2162,6 +2163,8 @@ static const struct {
 						BPF_CGROUP_UDP4_SENDMSG),
 	BPF_EAPROG_SEC("cgroup/sendmsg6",	BPF_PROG_TYPE_CGROUP_SOCK_ADDR,
 						BPF_CGROUP_UDP6_SENDMSG),
+	BPF_APROG_SEC("cgroup/file_open",	BPF_PROG_TYPE_FILE_FILTER,
+						BPF_CGROUP_FILE_OPEN),
 };
 
 #undef BPF_PROG_SEC_IMPL
tools/testing/selftests/bpf/bpf_helpers.h +2 -0
index 1d407b3494f9..2d5b1a2652e2 100644
@@ -155,6 +155,8 @@ static struct bpf_sock *(*bpf_sk_lookup_udp)(void *ctx,
 	(void *) BPF_FUNC_sk_lookup_udp;
 static int (*bpf_sk_release)(struct bpf_sock *sk) =
 	(void *) BPF_FUNC_sk_release;
+static int (*bpf_get_file_path)(struct bpf_file_info *file, void *buf, int size) =
+	(void *) BPF_FUNC_get_file_path;
 
 /* llvm builtin functions that eBPF C program may use to
  * emit BPF_LD_ABS and BPF_LD_IND instructions
Signature · 1 line
2.17.1
author maintainer
#18 lore raw

PATCH 6/6 selftests/bpf: add a test for BPF_CGROUP_FILE_OPEN

In reply to Alexei Starovoitov

add bpf test for BPF_CGROUP_FILE_OPEN which attaches to a temporary cgroup and
- disallows further access to cgroup v2 file system for processes within this cgroup
- figures out mount_id of /etc, disallows access to this mnt_id,
checks that /etc/hosts and /etc/hostname are no longer readable, re-allows access to /etc.
Note that /etc is likely mounted as part of /, so the test disallows access to / mount
- figures out dev/inode of /etc/hosts file and disallows access to this file

cgroup local storage is used to pass information between user space control program and bpf program attached to BPF_CGROUP_FILE_OPEN hook

---
 tools/testing/selftests/bpf/.gitignore        |   1 +
 tools/testing/selftests/bpf/Makefile          |   6 +-
 tools/testing/selftests/bpf/test_file_open.c  | 154 ++++++++++++++++++
 .../selftests/bpf/test_file_open_common.h     |  13 ++
 .../selftests/bpf/test_file_open_kern.c       |  48 ++++++
 5 files changed, 220 insertions(+), 2 deletions(-)
 create mode 100644 tools/testing/selftests/bpf/test_file_open.c
 create mode 100644 tools/testing/selftests/bpf/test_file_open_common.h
 create mode 100644 tools/testing/selftests/bpf/test_file_open_kern.c
tools/testing/selftests/bpf/.gitignore +1 -0
index 8a60c9b9892d..a332b39bed68 100644
@@ -25,3 +25,4 @@ test_cgroup_storage
 test_select_reuseport
 test_flow_dissector
 flow_dissector_load
+test_file_open
tools/testing/selftests/bpf/Makefile +4 -2
index 1381ab81099c..89a0fd955c8e 100644
@@ -24,7 +24,7 @@ TEST_GEN_PROGS = test_verifier test_tag test_maps test_lru_map test_lpm_map test
 	test_align test_verifier_log test_dev_cgroup test_tcpbpf_user \
 	test_sock test_btf test_sockmap test_lirc_mode2_user get_cgroup_id_user \
 	test_socket_cookie test_cgroup_storage test_select_reuseport test_section_names \
-	test_netcnt
+	test_netcnt test_file_open
 
 TEST_GEN_FILES = test_pkt_access.o test_xdp.o test_l4lb.o test_tcp_estats.o test_obj_id.o \
 	test_pkt_md_access.o test_xdp_redirect.o test_xdp_meta.o sockmap_parse_prog.o     \
@@ -36,7 +36,8 @@ TEST_GEN_FILES = test_pkt_access.o test_xdp.o test_l4lb.o test_tcp_estats.o test
 	test_get_stack_rawtp.o test_sockmap_kern.o test_sockhash_kern.o \
 	test_lwt_seg6local.o sendmsg4_prog.o sendmsg6_prog.o test_lirc_mode2_kern.o \
 	get_cgroup_id_kern.o socket_cookie_prog.o test_select_reuseport_kern.o \
-	test_skb_cgroup_id_kern.o bpf_flow.o netcnt_prog.o test_sk_lookup_kern.o
+	test_skb_cgroup_id_kern.o bpf_flow.o netcnt_prog.o test_sk_lookup_kern.o \
+	test_file_open_kern.o
 
 # Order correspond to 'make run_tests' order
 TEST_PROGS := test_kmod.sh \
@@ -74,6 +75,7 @@ $(OUTPUT)/test_progs: trace_helpers.c
 $(OUTPUT)/get_cgroup_id_user: cgroup_helpers.c
 $(OUTPUT)/test_cgroup_storage: cgroup_helpers.c
 $(OUTPUT)/test_netcnt: cgroup_helpers.c
+$(OUTPUT)/test_file_open: cgroup_helpers.c
 
 .PHONY: force
 
tools/testing/selftests/bpf/test_file_open.c new file +154 -0

154 lines not on this page · raw message

tools/testing/selftests/bpf/test_file_open_common.h new file +13 -0
new file mode 100644
index 000000000000..d07b1f0ba28b
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2018 Facebook */
+#ifndef __TEST_FILE_OPEN_COMMON_H
+#define __TEST_FILE_OPEN_COMMON_H
+
+struct test_file_open_config {
+	int mnt_id;
+	int dev_major;
+	int dev_minor;
+	int inode;
+};
+
+#endif
tools/testing/selftests/bpf/test_file_open_kern.c new file +48 -0

48 lines not on this page · raw message

Signature · 1 line
2.17.1