Hi,
This series is a proof of concept to fill some missing part of seccomp as the
ability to check syscall argument pointers or creating more dynamic security
policies. The goal of this new stackable Linux Security Module (LSM) called
Landlock is to allow any process, including unprivileged ones, to create
powerful security sandboxes comparable to the Seatbelt/XNU Sandbox or the
OpenBSD Pledge. This kind of sandbox help to mitigate the security impact of
bugs or unexpected/malicious behaviors in userland applications.
The first RFC [1] was focused on extending seccomp while staying at the syscall
level. This brought a working PoC but with some (mitigated) ToCToU race
conditions due to the seccomp ptrace hole (now fixed) and the non-atomic
syscall argument evaluation (hence the LSM hooks).
# Landlock LSM
This second RFC is a fresh revamp of the code while keeping some working ideas.
This series is mainly focused on LSM hooks, while keeping the possibility to
tied them to syscalls. This new code removes all race conditions by design. It
now use eBPF instead of a subset of cBPF (as used by seccomp-bpf). This allow
to remove the previous stacked cBPF hack to do complex access checks thanks to
dedicated eBPF functions. An eBPF program is still very limited (i.e. can only
call a whitelist of functions) and can not do a denial of service (i.e. no
loop). The other major improvement is the replacement of the previous custom
checker groups of syscall arguments with a new dedicated eBPF map to collect
and compare Landlock handles with system resources (e.g. files or network
connections).
The approach taken is to add the minimum amount of code while still allowing
the userland to create quite complex access rules. A dedicated security policy
language such as used by SELinux, AppArmor and other major LSMs is a lot of
code and dedicated to a trusted process (i.e. root/administrator).
# eBPF
To get an expressive language while still being safe and small, Landlock is
based on eBPF. Landlock should be usable by untrusted processes and must then
expose a minimal attack surface. The eBPF bytecode is minimal while powerful,
widely used and thought to be used by not so trusted application. Reusing this
code allows to not reproduce the same mistakes and minimize new code while
still taking a generic approach. There is only some new features like a new
kind of arraymap and few dedicated eBPF functions.
An eBPF program have access to an eBPF context which contains the LSM hook
arguments (as does seccomp-bpf with syscall arguments). They can be used
directly or passed to helper functions according to their types. It is then
possible to do complex access checks without race conditions nor inconsistent
evaluation (i.e. incorrect mirroring of the OS code and state [2]).
There is one new eBPF program type per LSM hook. This allow to statically check
which context access is performed by an eBPF program. This is needed to deny
kernel address leak and ensure the right use of LSM hook arguments with eBPF
functions. Moreover, this safe pointer handling remove the need for runtime
check or abstract data, which improve performances. Any user can add multiple
Landlock eBPF programs per LSM hook. They are stacked and evaluated one after
the other (cf. seccomp-bpf).
# LSM hooks
Contrary to syscalls, LSM hooks are security checkpoints and are not
architecture dependant. They are designed to match a security need reflected by
a security policy (e.g. access to a file). Exposing parts of some LSM hooks
instead of using the syscall API for sandboxing should help to avoid bugs and
hacks as encountered by the first RFC. Instead of redoing the work of the LSM
hooks through syscalls, we should use and expose them as does policies of
access control LSM.
Only a subset of the hooks are meaningful for an unprivileged sandbox mechanism
(e.g. file system or network access control). Landlock use an abstraction of
raw LSM hooks, which allow to deal with possible future API changes of the LSM
hook API. Moreover, thanks to the ePBF program typing (per LSM hook) used by
Landlock, it should not be hard to make such evolutions backward compatible.
# Use case scenario
First, a process need to create a new dedicated eBPF map containing handles.
This handles are references to system resources (e.g. file or directory) and
grouped in one or multiple maps to be efficiently managed and checked in
batches. This kind of map can be passed to Landlock eBPF functions to compare,
for example, with a file access request. The handles are only accessible from
the eBPF programs created by the same thread.
The loaded Landlock eBPF programs can be triggered by a seccomp filter
returning RET_LANDLOCK. In addition, a cookie (16-bit value) can be passed from
a seccomp filter to eBPF programs. This allow flexible security policies
between seccomp and Landlock.
A triggered Landlock eBPF program can then allow or deny an access, according
to its type (i.e. LSM hook), thanks to errno return values.
# Sandbox example with conditional access control depending on cgroup
$ mkdir /sys/fs/cgroup/sandboxed
$ ls /home
user1
$ LANDLOCK_CGROUPS='/sys/fs/cgroup/sandboxed' \
LANDLOCK_ALLOWED='/bin:/lib:/usr:/tmp:/proc/self/fd/0' \
./sandbox /bin/sh -i
$ ls /home
user1
$ echo $$ > /sys/fs/cgroup/sandboxed/cgroup.procs
$ ls /home
ls: cannot open directory '/home': Permission denied
# Current limitations and possible improvements
For now, eBPF programs can only return an errno code. It may be interesting to
be able to do other actions like seccomp-filter does (e.g. kill process). Such
features can easily be implemented but the main advantage of the current
approach is to be able to only execute eBPF programs until one return an errno
code instead of executing all programs like seccomp-filter does.
It is quite easy to add new eBPF functions to extend Landlock. The main concern
should be about the ability to leak information from the current process to
another one (e.g. through maps) to not reproduce the same security sensitive
behavior as ptrace.
This design does not seems too intrusive but is flexible enough to allow a
powerful sandbox mechanism accessible by any process on Linux. The use of
seccomp and Landlock is more suitable with the help of a userland library (e.g.
libseccomp) that could help to specify a high-level language to express a
security policy instead of raw eBPF programs. Moreover, thanks to LLVM, it is
possible to express an eBPF program with a subset of C.
# FAQ
## Why not use a language like used by SElinux or AppArmor?
This kind of LSMs are dedicated to administrators. They already manage the
system and are not a threat to the system security. However, seccomp, and
Landlock too, should be available to anyone, which potentially include
untrusted users and processes. To reduce the attack surface, Landlock should
expose the minimum amount of code, hence minimal complexity. Moreover, another
threat is to make accessible to a malicious code a new way to gain more
information. For example, Landlock features should not allow a program to get
the file owner if the directory containing this file is not readable. This data
could then be exfiltrated thanks to the access result. Thus, we should limit
the expressiveness of the available checks. The current approach is to do the
checks in such a way that only a comparison with an already accessed resource
(e.g. file descriptor) is possible. This allow to have a reference to compare
with, without exposing much information.
## Why a new LSM? Does SELinux, AppArmor, Smack or Tomoyo are not good enough?
The current access control LSMs are fine for their purpose which is to give the
*root* the ability to enforce a security policy for the *system*. What is
missing is a way to enforce a security policy for any applications by its
developer and *unprivileged user* as seccomp can do for raw syscall filtering.
Moreover, Landlock handles stacked hook programs from different users. It must
then ensure there is no possible malicious interactions between this programs.
Difference with other (access control) LSMs:
* not only dedicated to administrators (i.e. no_new_priv);
* limited kernel attack surface (e.g. policy parsing);
* helpers to compare complex objects (path/FD), no access to internal kernel
data (do not leak addresses);
* constraint policy rules/programs (no DoS: deterministic execution time);
* do not leak more information than the loader process can legitimately have
access to (minimize metadata inference): must compare from an already allowed
file (through a handle).
## Why does seccomp-filter is not enough?
A seccomp filter can access to raw syscall arguments which means that it is not
possible to filter according to pointed data as a file path. As demonstrated
the first version of this patch series, filtering at the syscall level is
complicated (e.g. need to take care of race conditions). This is mainly because
the access control checkpoints of the kernel are not at this high-level but
more underneath, at LSM hooks level. The LSM hooks are designed to handle this
kind of checks. This series use this approach to leverage the ability of
unprivileged users to limit themselves.
Cf. "What it isn't?" in Documentation/prctl/seccomp_filter.txt
## As a developer, why do I need this feature?
Landlock's goal is to help userland to limit its attack surface.
Security-conscious developers would like to protect users from a security bug
in their applications and the third-party dependencies they are using. Such a
bug can compromise all the user data and help an attacker to perform a
privilege escalation. Using an *unprivileged sandbox* feature such as Landlock
empower the developer with the ability to properly compartmentalize its
software and limit the impact of being compromised.
## As a user, why do I need a this feature?
Any user can already use seccomp-filter to whitelist a set of syscalls to
reduce the kernel attack surface for a set of processes. However an
unprivileged user can't create a security policy as the root user can thanks to
SELinux and other access control LSMs. Landlock allows any unprivileged user to
protect their data from being accessed by any process they run but only an
identified subset. User tools can be created to help create such a high-level
access control policy. This policy may not be powerful enough to express the
same policies as the current access control LSMs, because of the threat an
unprivileged user can be to the system, but it should be enough for most
use-cases (e.g. blacklist or whitelist a set of file hierarchies).
## Does Landlock can limit network access or other resources?
Limiting network access is obviously in the scope of Landlock but it is not yet
implemented. The main goal now is to get feedback about the whole concept, the
API and the file access control part. More access control types could be
implemented in the future.
## Why using the seccomp(2) syscall?
Landlock use the same semantic as seccomp to apply access rule restrictions. It
add a new layer of security for the current process which is inherited by its
childs. It make sense to use an unique access-restricting syscall (that should
be allowed by seccomp-filter rules) which can only drop privileges. Moreover, a
Landlock eBPF program could come from outside a process (e.g. passed through a
UNIX socket). It is then useful to differentiate the creation/load of Landlock
eBPF programs via bpf(2), from rule enforcing via seccomp(2).
# Differences from the RFC v1
* focus on the LSM hooks, not the syscalls:
* much more simple implementation
* does not need audit cache tricks to avoid race conditions
* more simple to use and more generic because using the LSM hook abstraction
directly
* more efficient because only checking in LSM hooks
* architecture agnostic
* switch from cBPF to eBPF:
* new eBPF program types dedicated to Landlock
* custom functions used by the eBPF program
* gain some new features (e.g. 10 registers, can load values of different
size, LLVM translator) but only a few functions allowed and a dedicated map
type
* new context: LSM hook ID, cookie and LSM hook arguments
* need to set the sysctl kernel.unprivileged_bpf_disable to 0 (default value)
to be able to load hook filters as unprivileged users
* smaller and simpler:
* no more checker groups but dedicated arraymap of handles
* simpler userland structs thanks to eBPF functions
* distinctive name: Landlock
[1] https://lkml.kernel.org/r/1458784008-16277-1-git-send-email-mic@digikod.net
[2] https://crypto.stanford.edu/cs155/papers/traps.pdf
This series can be applied on Linux 4.7 and be tested with
CONFIG_SECURITY_LANDLOCK and CONFIG_CGROUPS. I would really appreciate
constructive comments on the usability, architecture, code and userland API of
Landlock LSM.
Regards,
Mickaël Salaün (10):
landlock: Add Kconfig
bpf: Move u64_to_ptr() to BPF headers and inline it
bpf,landlock: Add a new arraymap type to deal with (Landlock) handles
seccomp: Split put_seccomp_filter() with put_seccomp()
seccomp: Handle Landlock
landlock: Add LSM hooks
landlock: Add errno check
landlock: Handle file system comparisons
landlock: Handle cgroups
samples/landlock: Add sandbox example
include/linux/bpf.h | 41 +++++
include/linux/lsm_hooks.h | 5 +
include/linux/seccomp.h | 54 ++++++-
include/uapi/asm-generic/errno-base.h | 1 +
include/uapi/linux/bpf.h | 103 ++++++++++++
include/uapi/linux/seccomp.h | 2 +
kernel/bpf/arraymap.c | 222 +++++++++++++++++++++++++
kernel/bpf/syscall.c | 18 ++-
kernel/bpf/verifier.c | 32 +++-
kernel/fork.c | 41 ++++-
kernel/seccomp.c | 211 +++++++++++++++++++++++-
samples/Makefile | 2 +-
samples/landlock/.gitignore | 1 +
samples/landlock/Makefile | 16 ++
samples/landlock/sandbox.c | 295 ++++++++++++++++++++++++++++++++++
security/Kconfig | 1 +
security/Makefile | 2 +
security/landlock/Kconfig | 19 +++
security/landlock/Makefile | 3 +
security/landlock/checker_cgroup.c | 96 +++++++++++
security/landlock/checker_cgroup.h | 18 +++
security/landlock/checker_fs.c | 183 +++++++++++++++++++++
security/landlock/checker_fs.h | 20 +++
security/landlock/lsm.c | 228 ++++++++++++++++++++++++++
security/security.c | 1 +
25 files changed, 1592 insertions(+), 23 deletions(-)
create mode 100644 samples/landlock/.gitignore
create mode 100644 samples/landlock/Makefile
create mode 100644 samples/landlock/sandbox.c
create mode 100644 security/landlock/Kconfig
create mode 100644 security/landlock/Makefile
create mode 100644 security/landlock/checker_cgroup.c
create mode 100644 security/landlock/checker_cgroup.h
create mode 100644 security/landlock/checker_fs.c
create mode 100644 security/landlock/checker_fs.h
create mode 100644 security/landlock/lsm.c
--
2.8.1
@@ -35,5 +35,6 @@#define EPIPE 32 /* Broken pipe */#define EDOM 33 /* Math argument out of domain of func */#define ERANGE 34 /* Math result not representable */+#define _ERRNO_LAST ERANGE#endif
@@ -104,8 +103,9 @@ static int landlock_run_prog(__u64 args[6])}}if(!ret){-if(cur_ret>MAX_ERRNO)-ret=MAX_ERRNO;+/* check errno to not mess with kernel code */+if(cur_ret>_ERRNO_LAST)+ret=EPERM;elseret=cur_ret;}
@@ -60,6 +60,8 @@ struct seccomp_filter {structbpf_prog*prog;};+staticvoidput_seccomp_filter(structseccomp_filter*filter);+/* Limit any path through the tree to 256KB worth of instructions. */#define MAX_INSNS_PER_PATH ((1 << 18) / sizeof(struct sock_filter))
This new arraymap looks like a set and brings new properties:
* strong typing of entries: the eBPF functions get the array type of
elements instead of CONST_PTR_TO_MAP (e.g.
CONST_PTR_TO_LANDLOCK_HANDLE_FS);
* force sequential filling (i.e. replace or append-only update), which
allow quick browsing of all entries.
This strong typing is useful to statically check if the content of a map
can be passed to an eBPF function. For example, Landlock use it to store
and manage kernel objects (e.g. struct file) instead of dealing with
userland raw data. This improve efficiency and ensure that an eBPF
program can only call functions with the right high-level arguments.
The enum bpf_map_handle_type list low-level types (e.g.
BPF_MAP_HANDLE_TYPE_LANDLOCK_FS_FD) which are identified when
updating a map entry (handle). This handle types are used to infer a
high-level arraymap type which are listed in enum bpf_map_array_type
(e.g. BPF_MAP_ARRAY_TYPE_LANDLOCK_FS).
For now, this new arraymap is only used by Landlock LSM (cf. next
commits) but it could be useful for other needs.
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Cc: Alexei Starovoitov <ast@kernel.org>
Cc: David S. Miller <davem@davemloft.net>
Cc: Daniel Borkmann <daniel@iogearbox.net>
Cc: James Morris <redacted>
Cc: Kees Cook <redacted>
---
include/linux/bpf.h | 18 +++++
include/uapi/linux/bpf.h | 18 +++++
kernel/bpf/arraymap.c | 181 +++++++++++++++++++++++++++++++++++++++++++++++
kernel/bpf/syscall.c | 9 ++-
kernel/bpf/verifier.c | 12 +++-
5 files changed, 235 insertions(+), 3 deletions(-)
@@ -491,3 +493,182 @@ static int __init register_perf_event_array_map(void)return0;}late_initcall(register_perf_event_array_map);+++#ifdef CONFIG_SECURITY_LANDLOCK+staticstructbpf_map*landlock_array_map_alloc(unionbpf_attr*attr)+{+if(attr->value_size!=sizeof(structlandlock_handle))+returnERR_PTR(-EINVAL);+attr->value_size=sizeof(structmap_landlock_handle);++returnarray_map_alloc(attr);+}++staticvoidlandlock_put_handle(structmap_landlock_handle*handle)+{+switch(handle->type){+/* TODO: add handle types */+default:+WARN_ON(1);+}+/* safeguard */+handle->type=BPF_MAP_HANDLE_TYPE_UNSPEC;+}++staticvoidlandlock_array_map_free(structbpf_map*map)+{+structbpf_array*array=container_of(map,structbpf_array,map);+inti;++synchronize_rcu();++for(i=0;i<array->n_entries;i++)+landlock_put_handle((structmap_landlock_handle*)+(array->value+array->elem_size*i));+kvfree(array);+}++staticenumbpf_map_array_typelandlock_get_array_type(+enumbpf_map_handle_typehandle_type)+{+switch(handle_type){+/* TODO: add handle types */+caseBPF_MAP_HANDLE_TYPE_UNSPEC:+default:+return-EINVAL;+}+}++#define FGET_OR_RET(file, fd) { \+file=fget(fd);\+if(unlikely(IS_ERR(file)))\+returnPTR_ERR(file);\+}++staticinlinelonglandlock_store_handle(structmap_landlock_handle*dst,+structlandlock_handle*khandle)+{+structpathkpath;++if(unlikely(!khandle))+return-EINVAL;++/* access control already done for the FD */++switch(khandle->type){+/* TODO: add handle types */+default:+WARN_ON(1);+path_put(&kpath);+return-EINVAL;+}+dst->type=khandle->type;+return0;+}++staticvoid*nop_map_lookup_elem(structbpf_map*map,void*key)+{+returnERR_PTR(-EINVAL);+}++/* called from syscall or from eBPF program */+staticintlandlock_array_map_update_elem(structbpf_map*map,void*key,+void*value,u64map_flags)+{+structbpf_array*array=container_of(map,structbpf_array,map);+u32index=*(u32*)key;+enumbpf_map_array_typearray_type;+intret;+structlandlock_handle*khandle=(structlandlock_handle*)value;+structmap_landlock_handle*handle,handle_new;++if(unlikely(map_flags>BPF_EXIST))+/* unknown flags */+return-EINVAL;++if(unlikely(index>=array->map.max_entries))+/* all elements were pre-allocated, cannot insert a new one */+return-E2BIG;++/* FIXME: add lock */+if(unlikely(index>array->n_entries))+/* only replace an existing entry or append a new one */+return-EINVAL;++/* TODO: handle all flags, not only BPF_ANY */+if(unlikely(map_flags==BPF_NOEXIST))+/* all elements already exist */+return-EEXIST;++if(unlikely(!khandle))+return-EINVAL;++array_type=landlock_get_array_type(khandle->type);+if(array_type<0)+returnarray_type;++if(!map->map_array_type){+/* set the initial set type */+map->map_array_type=array_type;+}elseif(map->map_array_type!=array_type){+return-EINVAL;+}++ret=landlock_store_handle(&handle_new,khandle);+if(!ret){+/* map->value_size == sizeof(struct map_landlock_handle) */+handle=(structmap_landlock_handle*)+(array->value+array->elem_size*index);+/* FIXME: make atomic update */+if(index<array->n_entries)+landlock_put_handle(handle);+*handle=handle_new;+/* TODO: use atomic_inc? */+if(index==array->n_entries)+array->n_entries++;+}+/* FIXME: unlock */++returnret;+}++/* called from syscall or from eBPF program */+staticintlandlock_array_map_delete_elem(structbpf_map*map,void*key)+{+structbpf_array*array=container_of(map,structbpf_array,map);+u32index=*(u32*)key;++/* only remove the last element */+/* TODO: use atomic_dec? */+if(array->n_entries&&index==array->n_entries-1){+array->n_entries--;+landlock_put_handle((structmap_landlock_handle*)+(array->value+array->elem_size*index));+return0;+}+return-EINVAL;+}++staticconststructbpf_map_opslandlock_array_ops={+.map_alloc=landlock_array_map_alloc,+.map_free=landlock_array_map_free,+.map_get_next_key=array_map_get_next_key,+.map_lookup_elem=nop_map_lookup_elem,+.map_update_elem=landlock_array_map_update_elem,+.map_delete_elem=landlock_array_map_delete_elem,+};++staticstructbpf_map_type_listlandlock_array_type__read_mostly={+.ops=&landlock_array_ops,+.type=BPF_MAP_TYPE_LANDLOCK_ARRAY,+};++staticint__initregister_landlock_array_map(void)+{+bpf_register_map_type(&landlock_array_type);+return0;+}++late_initcall(register_landlock_array_map);+#endif /* CONFIG_SECURITY_LANDLOCK */
@@ -1742,8 +1751,9 @@ static int check_ld_imm(struct verifier_env *env, struct bpf_insn *insn)/* replace_map_fd_with_map_ptr() should have caught bad ld_imm64 */BUG_ON(insn->src_reg!=BPF_PSEUDO_MAP_FD);-regs[insn->dst_reg].type=CONST_PTR_TO_MAP;regs[insn->dst_reg].map_ptr=ld_imm64_to_map_ptr(insn);+regs[insn->dst_reg].type=+bpf_reg_type_from_map(regs[insn->dst_reg].map_ptr);return0;}
@@ -0,0 +1,16 @@+# kbuild trick to avoid linker error. Can be omitted if a module is built.+obj-:=dummy.o++hostprogs-$(CONFIG_SECURITY_LANDLOCK):=sandbox+sandbox-objs:=sandbox.o++always:=$(hostprogs-y)++HOSTCFLAGS+=-I$(objtree)/usr/include++# Trick to allow make to be run from this directory+all:+$(MAKE)-C../../$$PWD/++clean:+$(MAKE)-C../../M=$$PWDclean
@@ -0,0 +1,295 @@+/*+*LandlockLSM-SandboxExample+*+*Copyright(C)2016MickaëlSalaün<mic@digikod.net>+*+*Thecodemaybeusedbyanyoneforanypurpose,andcanserveasastarting+*pointfordevelopingasandbox.+*/++#define _GNU_SOURCE+#include<errno.h>+#include<fcntl.h> /* open() */+#include<linux/bpf.h>+#include<linux/filter.h>+#include<linux/prctl.h>+#include<linux/seccomp.h>+#include<stddef.h>+#include<stdio.h>+#include<stdlib.h>+#include<string.h>+#include<sys/prctl.h>+#include<sys/syscall.h>+#include<unistd.h>++#include"../../tools/include/linux/filter.h"++#include"../bpf/libbpf.c"++#ifndef seccomp+staticintseccomp(unsignedintop,unsignedintflags,void*args)+{+errno=0;+returnsyscall(__NR_seccomp,op,flags,args);+}+#endif++#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))++staticintapply_sandbox(constchar**allowed_paths,intpath_nb,constchar**cgroup_paths,intcgroup_nb)+{+__u32key;+inti,ret=0,map_fs=-1,map_cg=-1,offset;++/* set up the test sandbox */+if(prctl(PR_SET_NO_NEW_PRIVS,1,0,0,0)){+perror("prctl(no_new_priv)");+return1;+}++/* register a new syscall filter */+structsock_filterfilter0[]={+/* pass a cookie containing 5 to the LSM hook filter */+BPF_STMT(BPF_RET|BPF_K,SECCOMP_RET_LANDLOCK|5),+};+structsock_fprogprog0={+.len=(unsignedshort)ARRAY_SIZE(filter0),+.filter=filter0,+};+if(seccomp(SECCOMP_SET_MODE_FILTER,0,&prog0)){+perror("seccomp(set_filter)");+return1;+}++if(path_nb){+map_fs=bpf_create_map(BPF_MAP_TYPE_LANDLOCK_ARRAY,sizeof(key),sizeof(structlandlock_handle),10,0);+if(map_fs<0){+fprintf(stderr,"bpf_create_map(fs");+perror(")");+return1;+}+for(key=0;key<path_nb;key++){+intfd=open(allowed_paths[key],O_RDONLY|O_CLOEXEC);+if(fd<0){+fprintf(stderr,"open(fs: \"%s\"",allowed_paths[key]);+perror(")");+return1;+}+structlandlock_handlehandle={+.type=BPF_MAP_HANDLE_TYPE_LANDLOCK_FS_FD,+.fd=(__u64)fd,+};++/* register a new LSM handle */+if(bpf_update_elem(map_fs,&key,&handle,BPF_ANY)){+fprintf(stderr,"bpf_update_elem(fs: \"%s\"",allowed_paths[key]);+perror(")");+close(fd);+return1;+}+close(fd);+}+}+if(cgroup_nb){+map_cg=bpf_create_map(BPF_MAP_TYPE_LANDLOCK_ARRAY,sizeof(key),sizeof(structlandlock_handle),10,0);+if(map_cg<0){+fprintf(stderr,"bpf_create_map(cgroup");+perror(")");+ret=1;+gotoerr_map_cgroup;+}+for(key=0;key<cgroup_nb;key++){+intfd=open(cgroup_paths[key],O_RDONLY|O_CLOEXEC);+if(fd<0){+fprintf(stderr,"open(cgroup: \"%s\"",cgroup_paths[key]);+perror(")");+return1;+}+structlandlock_handlehandle={+.type=BPF_MAP_HANDLE_TYPE_LANDLOCK_CGROUP_FD,+.fd=(__u64)fd,+};++/* register a new LSM handle */+if(bpf_update_elem(map_cg,&key,&handle,BPF_ANY)){+fprintf(stderr,"bpf_update_elem(cgroup: \"%s\"",cgroup_paths[key]);+perror(")");+close(fd);+return1;+}+close(fd);+}+}++/* load a LSM filter hook (eBPF) */+structbpf_insnhook_pre[]={+/* save context */+BPF_MOV64_REG(BPF_REG_6,BPF_REG_1),++/* check our cookie (not used in this example) */+BPF_LDX_MEM(BPF_H,BPF_REG_0,BPF_REG_6,offsetof(structlandlock_data,cookie)),+BPF_JMP_IMM(BPF_JEQ,BPF_REG_0,5,2),+BPF_MOV32_IMM(BPF_REG_0,0),+BPF_EXIT_INSN(),+};+structbpf_insnhook_path[]={+/* specify an option, if any */+BPF_MOV32_IMM(BPF_REG_1,0),+/* handles to compare with */+BPF_LD_MAP_FD(BPF_REG_2,map_fs),+BPF_MOV64_IMM(BPF_REG_3,BPF_MAP_ARRAY_OP_OR),+/* hook argument (struct file) */+BPF_LDX_MEM(BPF_DW,BPF_REG_4,BPF_REG_6,offsetof(structlandlock_data,args[0])),+/* checker function */+BPF_EMIT_CALL(BPF_FUNC_landlock_cmp_fs_beneath_with_struct_file),++/* if the checked path is beneath the handle */+BPF_JMP_IMM(BPF_JNE,BPF_REG_0,0,2),+BPF_MOV32_IMM(BPF_REG_0,0),+BPF_EXIT_INSN(),+/* allow anonymous mapping */+BPF_JMP_IMM(BPF_JNE,BPF_REG_0,-ENOENT,2),+BPF_MOV32_IMM(BPF_REG_0,0),+BPF_EXIT_INSN(),+/* deny by default, if any error */+BPF_JMP_IMM(BPF_JGE,BPF_REG_0,0,2),+BPF_MOV32_IMM(BPF_REG_0,EACCES),+BPF_EXIT_INSN(),+};+structbpf_insnhook_cgroup[]={+/* specify an option, if any */+BPF_MOV32_IMM(BPF_REG_1,0),+/* handles to compare with */+BPF_LD_MAP_FD(BPF_REG_2,map_cg),+BPF_MOV64_IMM(BPF_REG_3,BPF_MAP_ARRAY_OP_OR),+/* checker function */+BPF_EMIT_CALL(BPF_FUNC_landlock_cmp_cgroup_beneath),++/* if the current process is in a blacklisted cgroup */+BPF_JMP_IMM(BPF_JEQ,BPF_REG_0,1,2),+BPF_MOV32_IMM(BPF_REG_0,EACCES),+BPF_EXIT_INSN(),+};+structbpf_insnhook_post[]={+BPF_MOV32_IMM(BPF_REG_0,0),+BPF_EXIT_INSN(),+};+/* deny all processes if no cgroup is specified */+if(cgroup_nb==0){+hook_post[0]=BPF_MOV32_IMM(BPF_REG_0,EACCES);+}++unsignedlonghook_size=sizeof(hook_pre)+sizeof(hook_path)*(path_nb?1:0)++sizeof(hook_cgroup)*(cgroup_nb?1:0)+sizeof(hook_post);++structbpf_insn*hook0=malloc(hook_size);+if(!hook0){+perror("malloc");+ret=1;+gotoerr_alloc;+}+memcpy(hook0,hook_pre,sizeof(hook_pre));+offset=sizeof(hook_pre)/sizeof(hook0[0]);+if(path_nb){+memcpy(hook0+offset,hook_path,sizeof(hook_path));+offset+=sizeof(hook_path)/sizeof(hook0[0]);+}+if(cgroup_nb){+memcpy(hook0+offset,hook_cgroup,sizeof(hook_cgroup));+offset+=sizeof(hook_cgroup)/sizeof(hook0[0]);+}+memcpy(hook0+offset,hook_post,sizeof(hook_post));++/* TODO: handle inode_permission hook (e.g. chdir) */+enumbpf_prog_typehook_types[]={+BPF_PROG_TYPE_LANDLOCK_FILE_OPEN,+BPF_PROG_TYPE_LANDLOCK_FILE_PERMISSION,+BPF_PROG_TYPE_LANDLOCK_MMAP_FILE,+};+for(i=0;i<ARRAY_SIZE(hook_types);i++){+intbpf0=bpf_prog_load(hook_types[i],+hook0,hook_size,"GPL",0);+if(bpf0==-1){+perror("bpf");+fprintf(stderr,"%s",bpf_log_buf);+ret=1;+break;+}+if(seccomp(SECCOMP_SET_LANDLOCK_HOOK,0,&bpf0)){+perror("seccomp(set_hook)");+ret=1;+close(bpf0);+break;+}+close(bpf0);+}++free(hook0);+err_alloc:+if(cgroup_nb){+close(map_cg);+}+err_map_cgroup:+if(path_nb){+close(map_fs);+}+returnret;+}++#define ENV_FS_PATH_NAME "LANDLOCK_ALLOWED"+#define ENV_CGROUP_PATH_NAME "LANDLOCK_CGROUPS"+#define ENV_PATH_TOKEN ":"++staticintparse_path(char*env_path,constchar***path_list){+inti,path_nb=0;++if(env_path){+path_nb++;+for(i=0;env_path[i];i++){+if(env_path[i]==ENV_PATH_TOKEN[0]){+path_nb++;+}+}+}+*path_list=malloc(path_nb*sizeof(**path_list));+for(i=0;i<path_nb;i++){+(*path_list)[i]=strsep(&env_path,ENV_PATH_TOKEN);+}++returnpath_nb;+}++intmain(intargc,char*constargv[],char*const*envp)+{+char*cmd_path;+char*env_path_allowed,*env_path_cgroup;+intpath_nb,cgroup_nb;+constchar**sb_paths=NULL;+constchar**cg_paths=NULL;+char*const*cmd_argv;++env_path_allowed=getenv(ENV_FS_PATH_NAME);+if(env_path_allowed)+env_path_allowed=strdup(env_path_allowed);+env_path_cgroup=getenv(ENV_CGROUP_PATH_NAME);+if(env_path_cgroup)+env_path_cgroup=strdup(env_path_cgroup);++if(argc<2){+fprintf(stderr,"usage: %s <cmd> [args]...\n\n",argv[0]);+fprintf(stderr,"Environment variables containing paths, each separated by a colon:\n");+fprintf(stderr,"* %s (whitelist of allowed files and directories)\n",ENV_FS_PATH_NAME);+fprintf(stderr,"* %s (optional cgroups for which the sandbox is enabled)\n",ENV_CGROUP_PATH_NAME);+fprintf(stderr,"\nexample:\n%s='/sys/fs/cgroup/sandboxed' %s='/bin:/lib:/usr:/tmp:/proc/self/fd/0' %s /bin/sh -i\n",ENV_CGROUP_PATH_NAME,ENV_FS_PATH_NAME,argv[0]);+return1;+}+path_nb=parse_path(env_path_allowed,&sb_paths);+cgroup_nb=parse_path(env_path_cgroup,&cg_paths);+cmd_path=argv[1];+cmd_argv=argv+1;+if(apply_sandbox(sb_paths,path_nb,cg_paths,cgroup_nb))+return1;+execve(cmd_path,cmd_argv,envp);+perror("execve");+return1;+}
Add an eBPF function bpf_landlock_cmp_cgroup_beneath(opt, map, map_op)
to compare the current process cgroup with a cgroup handle, The handle
can match the current cgroup if it is the same or a child. This allows
to make conditional rules according to the current cgroup.
A cgroup handle is a map entry created from a file descriptor referring
a cgroup directory (e.g. by opening /sys/fs/cgroup/X). In this case, the
map entry is of type BPF_MAP_HANDLE_TYPE_LANDLOCK_CGROUP_FD and the
inferred array map is of type BPF_MAP_ARRAY_TYPE_LANDLOCK_CGROUP.
An unprivileged process can create and manipulate cgroups thanks to
cgroup delegation.
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Cc: Kees Cook <redacted>
Cc: Alexei Starovoitov <ast@kernel.org>
Cc: James Morris <redacted>
Cc: Serge E. Hallyn <serge@hallyn.com>
Cc: David S. Miller <davem@davemloft.net>
Cc: Daniel Borkmann <daniel@iogearbox.net>
---
include/linux/bpf.h | 8 ++++
include/uapi/linux/bpf.h | 15 ++++++
kernel/bpf/arraymap.c | 30 ++++++++++++
kernel/bpf/verifier.c | 6 +++
security/landlock/Kconfig | 3 ++
security/landlock/Makefile | 2 +-
security/landlock/checker_cgroup.c | 96 ++++++++++++++++++++++++++++++++++++++
security/landlock/checker_cgroup.h | 18 +++++++
security/landlock/lsm.c | 8 ++++
9 files changed, 185 insertions(+), 1 deletion(-)
create mode 100644 security/landlock/checker_cgroup.c
create mode 100644 security/landlock/checker_cgroup.h
@@ -85,6 +88,7 @@ enum bpf_arg_type {ARG_PTR_TO_STRUCT_FILE,/* pointer to struct file */ARG_PTR_TO_STRUCT_CRED,/* pointer to struct cred */ARG_CONST_PTR_TO_LANDLOCK_HANDLE_FS,/* pointer to Landlock FS handle */+ARG_CONST_PTR_TO_LANDLOCK_HANDLE_CGROUP,/* pointer to Landlock cgroup handle */};/* type of values returned from helper functions */
@@ -0,0 +1,96 @@+/*+*LandlockLSM-cgroupCheckers+*+*Copyright(C)2016MickaëlSalaün<mic@digikod.net>+*+*Thisprogramisfreesoftware;youcanredistributeitand/ormodify+*itunderthetermsoftheGNUGeneralPublicLicenseversion2,as+*publishedbytheFreeSoftwareFoundation.+*/++#ifdef CONFIG_CGROUPS++#include<asm/current.h>+#include<linux/bpf.h> /* enum bpf_map_array_op */+#include<linux/cgroup-defs.h> /* struct cgroup_subsys_state */+#include<linux/cgroup.h> /* cgroup_is_descendant(), task_css_set() */+#include<linux/errno.h>++#include"checker_cgroup.h"+++/*+*bpf_landlock_cmp_cgroup_beneath+*+*Cf.include/uapi/linux/bpf.h+*/+staticinlineu64bpf_landlock_cmp_cgroup_beneath(u64r1_option,u64r2_map,+u64r3_map_op,u64r4,u64r5)+{+u8option=(u8)r1_option;+structbpf_map*map=(structbpf_map*)(unsignedlong)r2_map;+enumbpf_map_array_opmap_op=r3_map_op;+structbpf_array*array=container_of(map,structbpf_array,map);+structcgroup*cg1,*cg2;+structmap_landlock_handle*handle;+inti;++/* ARG_CONST_PTR_TO_LANDLOCK_HANDLE_CGROUP is an arraymap */+if(unlikely(!map)){+WARN_ON(1);+return-EFAULT;+}+if(unlikely((option|_LANDLOCK_FLAG_OPT_MASK)!=_LANDLOCK_FLAG_OPT_MASK))+return-EINVAL;++/* for now, only handle OP_OR */+switch(map_op){+caseBPF_MAP_ARRAY_OP_OR:+break;+caseBPF_MAP_ARRAY_OP_UNSPEC:+caseBPF_MAP_ARRAY_OP_AND:+caseBPF_MAP_ARRAY_OP_XOR:+default:+return-EINVAL;+}++synchronize_rcu();++for(i=0;i<array->n_entries;i++){+handle=(structmap_landlock_handle*)+(array->value+array->elem_size*i);++/* protected by the proto types, should not happen */+if(unlikely(handle->type!=BPF_MAP_HANDLE_TYPE_LANDLOCK_CGROUP_FD)){+WARN_ON(1);+return-EFAULT;+}+if(unlikely(!handle->css)){+WARN_ON(1);+return-EFAULT;+}++if(option&LANDLOCK_FLAG_OPT_REVERSE){+cg1=handle->css->cgroup;+cg2=task_css_set(current)->dfl_cgrp;+}else{+cg1=task_css_set(current)->dfl_cgrp;+cg2=handle->css->cgroup;+}++if(cgroup_is_descendant(cg1,cg2))+return0;+}+return1;+}++conststructbpf_func_protobpf_landlock_cmp_cgroup_beneath_proto={+.func=bpf_landlock_cmp_cgroup_beneath,+.gpl_only=true,+.ret_type=RET_INTEGER,+.arg1_type=ARG_ANYTHING,+.arg2_type=ARG_CONST_PTR_TO_LANDLOCK_HANDLE_CGROUP,+.arg3_type=ARG_ANYTHING,+};++#endif /* CONFIG_CGROUPS */
Add LSM hooks which can be used by userland through Landlock (eBPF)
programs. This programs are limited to a whitelist of functions (cf.
next commit). The eBPF program context is depicted by the struct
landlock_data (cf. include/uapi/linux/bpf.h):
* hook: LSM hook ID (useful when using the same program for multiple LSM
hooks);
* cookie: the 16-bit value from the seccomp filter that triggered this
Landlock program;
* args[6]: array of LSM hook arguments.
The LSM hook arguments can contain raw values as integers or
(unleakable) pointers. The only way to use the pointers are to pass them
to an eBPF function according to their types (e.g. the
bpf_landlock_cmp_fs_beneath_with_struct_file function can use a struct
file pointer).
For now, there is three hooks for file system access control:
* file_open;
* file_permission;
* mmap_file.
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Cc: Alexei Starovoitov <ast@kernel.org>
Cc: Kees Cook <redacted>
Cc: Andy Lutomirski <luto@amacapital.net>
Cc: Will Drewry <wad@chromium.org>
Cc: James Morris <redacted>
Cc: Serge E. Hallyn <serge@hallyn.com>
Cc: David S. Miller <davem@davemloft.net>
Cc: Daniel Borkmann <daniel@iogearbox.net>
---
include/linux/bpf.h | 7 ++
include/linux/lsm_hooks.h | 5 ++
include/uapi/linux/bpf.h | 20 +++++
kernel/bpf/syscall.c | 3 +
kernel/bpf/verifier.c | 8 ++
kernel/seccomp.c | 7 +-
security/Makefile | 2 +
security/landlock/Makefile | 3 +
security/landlock/lsm.c | 211 +++++++++++++++++++++++++++++++++++++++++++++
security/security.c | 1 +
10 files changed, 265 insertions(+), 2 deletions(-)
create mode 100644 security/landlock/Makefile
create mode 100644 security/landlock/lsm.c
@@ -81,6 +81,9 @@ enum bpf_arg_type {ARG_PTR_TO_CTX,/* pointer to context */ARG_ANYTHING,/* any (initialized) argument is ok */++ARG_PTR_TO_STRUCT_FILE,/* pointer to struct file */+ARG_PTR_TO_STRUCT_CRED,/* pointer to struct cred */};/* type of values returned from helper functions */
Add eBPF functions to compare file system access with a Landlock file
system handle:
* bpf_landlock_cmp_fs_prop_with_struct_file(prop, map, map_op, file)
This function allows to compare the dentry, inode, device or mount
point of the currently accessed file, with a reference handle.
* bpf_landlock_cmp_fs_beneath_with_struct_file(opt, map, map_op, file)
This function allows an eBPF program to check if the current accessed
file is the same or in the hierarchy of a reference handle.
The goal of file system handle is to abstract kernel objects such as a
struct file or a struct inode. Userland can create this kind of handle
thanks to the BPF_MAP_UPDATE_ELEM command. The element is a struct
landlock_handle containing the handle type (e.g.
BPF_MAP_HANDLE_TYPE_LANDLOCK_FS_FD) and a file descriptor. This could
also be any descriptions able to match a struct file or a struct inode
(e.g. path or glob string).
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Cc: Kees Cook <redacted>
Cc: Alexei Starovoitov <ast@kernel.org>
Cc: James Morris <redacted>
Cc: Serge E. Hallyn <serge@hallyn.com>
Cc: David S. Miller <davem@davemloft.net>
Cc: Daniel Borkmann <daniel@iogearbox.net>
---
include/linux/bpf.h | 4 +-
include/uapi/linux/bpf.h | 52 +++++++++++-
kernel/bpf/arraymap.c | 17 +++-
kernel/bpf/verifier.c | 6 ++
security/landlock/Makefile | 2 +-
security/landlock/checker_fs.c | 183 +++++++++++++++++++++++++++++++++++++++++
security/landlock/checker_fs.h | 20 +++++
security/landlock/lsm.c | 11 ++-
8 files changed, 288 insertions(+), 7 deletions(-)
create mode 100644 security/landlock/checker_fs.c
create mode 100644 security/landlock/checker_fs.h
@@ -84,6 +84,7 @@ enum bpf_arg_type {ARG_PTR_TO_STRUCT_FILE,/* pointer to struct file */ARG_PTR_TO_STRUCT_CRED,/* pointer to struct cred */+ARG_CONST_PTR_TO_LANDLOCK_HANDLE_FS,/* pointer to Landlock FS handle */};/* type of values returned from helper functions */
@@ -0,0 +1,183 @@+/*+*LandlockLSM-FileSystemCheckers+*+*Copyright(C)2016MickaëlSalaün<mic@digikod.net>+*+*Thisprogramisfreesoftware;youcanredistributeitand/ormodify+*itunderthetermsoftheGNUGeneralPublicLicenseversion2,as+*publishedbytheFreeSoftwareFoundation.+*/++#include<linux/bpf.h> /* enum bpf_map_array_op */+#include<linux/errno.h>+#include<linux/fs.h> /* path_is_under() */+#include<linux/path.h> /* struct path */++#include"checker_fs.h"++#define EQUAL_NOT_NULL(a, b) (a && a == b)++/*+*bpf_landlock_cmp_fs_prop_with_struct_file+*+*Cf.include/uapi/linux/bpf.h+*/+staticinlineu64bpf_landlock_cmp_fs_prop_with_struct_file(u64r1_property,+u64r2_map,u64r3_map_op,u64r4_file,u64r5)+{+u8property=(u8)r1_property;+structbpf_map*map=(structbpf_map*)(unsignedlong)r2_map;+enumbpf_map_array_opmap_op=r3_map_op;+structfile*file=(structfile*)(unsignedlong)r4_file;+structbpf_array*array=container_of(map,structbpf_array,map);+structpath*p1,*p2;+structmap_landlock_handle*handle;+inti;+boolresult_dentry=!(property&LANDLOCK_FLAG_FS_DENTRY);+boolresult_inode=!(property&LANDLOCK_FLAG_FS_INODE);+boolresult_device=!(property&LANDLOCK_FLAG_FS_DEVICE);+boolresult_mount=!(property&LANDLOCK_FLAG_FS_MOUNT);++/* ARG_CONST_PTR_TO_LANDLOCK_HANDLE_FS is a arraymap */+if(unlikely(!map)){+WARN_ON(1);+return-EFAULT;+}+if(unlikely(!file))+return-ENOENT;+if(unlikely((property|_LANDLOCK_FLAG_FS_MASK)!=_LANDLOCK_FLAG_FS_MASK))+return-EINVAL;++/* for now, only handle OP_OR */+switch(map_op){+caseBPF_MAP_ARRAY_OP_OR:+break;+caseBPF_MAP_ARRAY_OP_UNSPEC:+caseBPF_MAP_ARRAY_OP_AND:+caseBPF_MAP_ARRAY_OP_XOR:+default:+return-EINVAL;+}++synchronize_rcu();++for(i=0;i<array->n_entries;i++){+handle=(structmap_landlock_handle*)+(array->value+array->elem_size*i);++if(handle->type!=BPF_MAP_HANDLE_TYPE_LANDLOCK_FS_FD){+WARN_ON(1);+return-EFAULT;+}++p1=&handle->file->f_path;+p2=&file->f_path;+if(unlikely(!p1||!p2)){+WARN_ON(1);+return-EFAULT;+}++if(!result_dentry&&p1->dentry==p2->dentry)+result_dentry=true;+/* TODO: use d_inode_rcu() instead? */+if(!result_inode+&&EQUAL_NOT_NULL(d_inode(p1->dentry)->i_ino,+d_inode(p2->dentry)->i_ino))+result_inode=true;+/* check superblock instead of device major/minor */+if(!result_device+&&EQUAL_NOT_NULL(d_inode(p1->dentry)->i_sb,+d_inode(p2->dentry)->i_sb))+result_device=true;+if(!result_mount&&EQUAL_NOT_NULL(p1->mnt,p2->mnt))+result_mount=true;+if(result_dentry&&result_inode&&result_device&&result_mount)+return0;+}+return1;+}++conststructbpf_func_protobpf_landlock_cmp_fs_prop_with_struct_file_proto={+.func=bpf_landlock_cmp_fs_prop_with_struct_file,+.gpl_only=true,+.ret_type=RET_INTEGER,+.arg1_type=ARG_ANYTHING,+.arg2_type=ARG_CONST_PTR_TO_LANDLOCK_HANDLE_FS,+.arg3_type=ARG_ANYTHING,+.arg4_type=ARG_PTR_TO_STRUCT_FILE,+};++/*+*bpf_landlock_cmp_fs_beneath_with_struct_file+*+*Cf.include/uapi/linux/bpf.h+*/+staticinlineu64bpf_landlock_cmp_fs_beneath_with_struct_file(u64r1_option,+u64r2_map,u64r3_map_op,u64r4_file,u64r5)+{+u8option=(u8)r1_option;+structbpf_map*map=(structbpf_map*)(unsignedlong)r2_map;+enumbpf_map_array_opmap_op=r3_map_op;+structfile*file=(structfile*)(unsignedlong)r4_file;+structbpf_array*array=container_of(map,structbpf_array,map);+structpath*p1,*p2;+structmap_landlock_handle*handle;+inti;++/* ARG_CONST_PTR_TO_LANDLOCK_HANDLE_FS is an arraymap */+if(unlikely(!map)){+WARN_ON(1);+return-EFAULT;+}+/* @file can be null for anonymous mmap */+if(unlikely(!file))+return-ENOENT;+if(unlikely((option|_LANDLOCK_FLAG_OPT_MASK)!=_LANDLOCK_FLAG_OPT_MASK))+return-EINVAL;++/* for now, only handle OP_OR */+switch(map_op){+caseBPF_MAP_ARRAY_OP_OR:+break;+caseBPF_MAP_ARRAY_OP_UNSPEC:+caseBPF_MAP_ARRAY_OP_AND:+caseBPF_MAP_ARRAY_OP_XOR:+default:+return-EINVAL;+}++synchronize_rcu();++for(i=0;i<array->n_entries;i++){+handle=(structmap_landlock_handle*)+(array->value+array->elem_size*i);++/* protected by the proto types, should not happen */+if(unlikely(handle->type!=BPF_MAP_HANDLE_TYPE_LANDLOCK_FS_FD)){+WARN_ON(1);+return-EINVAL;+}++if(option&LANDLOCK_FLAG_OPT_REVERSE){+p1=&file->f_path;+p2=&handle->file->f_path;+}else{+p1=&handle->file->f_path;+p2=&file->f_path;+}++if(path_is_under(p2,p1))+return0;+}+return1;+}++conststructbpf_func_protobpf_landlock_cmp_fs_beneath_with_struct_file_proto={+.func=bpf_landlock_cmp_fs_beneath_with_struct_file,+.gpl_only=true,+.ret_type=RET_INTEGER,+.arg1_type=ARG_ANYTHING,+.arg2_type=ARG_CONST_PTR_TO_LANDLOCK_HANDLE_FS,+.arg3_type=ARG_ANYTHING,+.arg4_type=ARG_PTR_TO_STRUCT_FILE,+};
A Landlock program can be triggered when a seccomp filter return
RET_LANDLOCK. Moreover, it is possible to return a 16-bit cookie which
will be readable by the Landlock programs.
Only seccomp filters loaded from the same thread and before a Landlock
program can trigger it. Multiple Landlock programs can be triggered by
one or more seccomp filters. This way, each RET_LANDLOCK (with specific
cookie) will trigger all the allowed Landlock programs once.
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Cc: Kees Cook <redacted>
Cc: Andy Lutomirski <luto@amacapital.net>
Cc: Will Drewry <wad@chromium.org>
Cc: Andrew Morton <akpm@linux-foundation.org>
---
include/linux/seccomp.h | 49 +++++++++++
include/uapi/linux/seccomp.h | 2 +
kernel/fork.c | 39 ++++++++-
kernel/seccomp.c | 190 ++++++++++++++++++++++++++++++++++++++++++-
4 files changed, 275 insertions(+), 5 deletions(-)
@@ -1200,9 +1205,12 @@ static int copy_signal(unsigned long clone_flags, struct task_struct *tsk)return0;}-staticvoidcopy_seccomp(structtask_struct*p)+staticintcopy_seccomp(structtask_struct*p){#ifdef CONFIG_SECCOMP+#ifdef CONFIG_SECURITY_LANDLOCK+structseccomp_landlock_ret*ret_walk;+#endif /* CONFIG_SECURITY_LANDLOCK *//**Mustbecalledwithsighand->lockheld,whichiscommonto*allthreadsinthegroup.Holdingcred_guard_mutexisnot
@@ -1213,7 +1221,27 @@ static void copy_seccomp(struct task_struct *p)/* Ref-count the new filter user, and assign it. */get_seccomp_filter(current);-p->seccomp=current->seccomp;+p->seccomp.mode=current->seccomp.mode;+p->seccomp.filter=current->seccomp.filter;+#ifdef CONFIG_SECURITY_LANDLOCK+/* No copy for: landlock_filter, landlock_handle */+p->seccomp.landlock_prog=current->seccomp.landlock_prog;+if(p->seccomp.landlock_prog)+atomic_inc(&p->seccomp.landlock_prog->usage);+/* Deep copy for landlock_ret to avoid allocating for each syscall */+for(ret_walk=current->seccomp.landlock_ret;+ret_walk;ret_walk=ret_walk->prev){+structseccomp_landlock_ret*ret_new;++ret_new=dup_landlock_ret(ret_walk);+if(IS_ERR(ret_new)){+put_landlock_ret(p->seccomp.landlock_ret);+returnPTR_ERR(ret_new);+}+ret_new->prev=p->seccomp.landlock_ret;+p->seccomp.landlock_ret=ret_new;+}+#endif /* CONFIG_SECURITY_LANDLOCK *//**Explicitlyenableno_new_privshereincaseitgotset
@@ -191,6 +204,14 @@ static u32 seccomp_run_filters(struct seccomp_data *sd)populate_seccomp_data(&sd_local);sd=&sd_local;}+#ifdef CONFIG_SECURITY_LANDLOCK+for(landlock_ret=init_landlock_ret;+landlock_ret;+landlock_ret=landlock_ret->prev){+/* No need to clean the cookie. */+landlock_ret->triggered=false;+}+#endif /* CONFIG_SECURITY_LANDLOCK *//**AllfiltersinthelistareevaluatedandthelowestBPFreturn
@@ -426,6 +466,9 @@ static long seccomp_attach_filter(unsigned int flags,{unsignedlongtotal_insns;structseccomp_filter*walker;+#ifdef CONFIG_SECURITY_LANDLOCK+structseccomp_landlock_ret*landlock_ret;+#endif /* CONFIG_SECURITY_LANDLOCK */assert_spin_locked(¤t->sighand->siglock);
@@ -450,6 +493,21 @@ static long seccomp_attach_filter(unsigned int flags,*taskreference.*/filter->prev=current->seccomp.filter;+#ifdef CONFIG_SECURITY_LANDLOCK+filter->landlock_prev=current->seccomp.landlock_filter;+current->seccomp.landlock_filter=filter;++/* Dedicated Landlock result */+landlock_ret=kmalloc(sizeof(*landlock_ret),GFP_KERNEL);+if(!landlock_ret)+return-ENOMEM;+landlock_ret->prev=current->seccomp.landlock_ret;+atomic_inc(&filter->usage);+landlock_ret->filter=filter;+landlock_ret->cookie=0;+landlock_ret->triggered=false;+current->seccomp.landlock_ret=landlock_ret;+#endif /* CONFIG_SECURITY_LANDLOCK */current->seccomp.filter=filter;/* Now that the new filter is in place, synchronize to all threads. */
@@ -459,6 +517,55 @@ static long seccomp_attach_filter(unsigned int flags,return0;}+#ifdef CONFIG_SECURITY_LANDLOCK+structseccomp_landlock_ret*dup_landlock_ret(+structseccomp_landlock_ret*ret_orig)+{+structseccomp_landlock_ret*ret_new;++if(!ret_orig)+returnNULL;+ret_new=kmalloc(sizeof(*ret_new),GFP_KERNEL);+if(!ret_new)+returnERR_PTR(-ENOMEM);+ret_new->filter=ret_orig->filter;+if(ret_new->filter)+atomic_inc(&ret_new->filter->usage);+ret_new->cookie=0;+ret_new->triggered=false;+ret_new->prev=NULL;+returnret_new;+}++staticvoidput_landlock_prog(structseccomp_landlock_prog*landlock_prog)+{+structseccomp_landlock_prog*orig=landlock_prog;++/* Clean up single-reference branches iteratively. */+while(orig&&atomic_dec_and_test(&orig->usage)){+structseccomp_landlock_prog*freeme=orig;++put_seccomp_filter(orig->filter);+bpf_prog_put(orig->prog);+orig=orig->prev;+kfree(freeme);+}+}++voidput_landlock_ret(structseccomp_landlock_ret*landlock_ret)+{+structseccomp_landlock_ret*orig=landlock_ret;++while(orig){+structseccomp_landlock_ret*freeme=orig;++put_seccomp_filter(orig->filter);+orig=orig->prev;+kfree(freeme);+}+}+#endif /* CONFIG_SECURITY_LANDLOCK */+/* get_seccomp_filter - increments the reference count of the filter on @tsk */voidget_seccomp_filter(structtask_struct*tsk){
@@ -485,7 +592,9 @@ static void put_seccomp_filter(struct seccomp_filter *filter)/* Clean up single-reference branches iteratively. */while(orig&&atomic_dec_and_test(&orig->usage)){structseccomp_filter*freeme=orig;+orig=orig->prev;+/* must not put orig->landlock_prev */seccomp_filter_free(freeme);}}
@@ -609,6 +722,8 @@ static u32 __seccomp_phase1_filter(int this_syscall, struct seccomp_data *sd)caseSECCOMP_RET_TRACE:returnfilter_ret;/* Save the rest for phase 2. */+caseSECCOMP_RET_LANDLOCK:+/* fall through */caseSECCOMP_RET_ALLOW:returnSECCOMP_PHASE1_OK;
@@ -814,6 +929,75 @@ static inline long seccomp_set_mode_filter(unsigned int flags,}#endif++#ifdef CONFIG_SECURITY_LANDLOCK++/* Limit Landlock programs to 256KB. */+#define LANDLOCK_PROG_LIST_MAX_PAGES (1 << 6)++staticlonglandlock_set_hook(unsignedintflags,constchar__user*user_bpf_fd)+{+longresult;+unsignedlongprog_list_pages;+structseccomp_landlock_prog*landlock_prog,*cp_walker;+intbpf_fd;+structbpf_prog*prog;++if(!task_no_new_privs(current)&&+security_capable_noaudit(current_cred(),+current_user_ns(),CAP_SYS_ADMIN)!=0)+return-EACCES;+if(!user_bpf_fd)+return-EINVAL;++/* could be used for TSYNC */+if(flags)+return-EINVAL;++if(copy_from_user(&bpf_fd,user_bpf_fd,sizeof(user_bpf_fd)))+return-EFAULT;+prog=bpf_prog_get(bpf_fd);+if(IS_ERR(prog))+returnPTR_ERR(prog);+switch(prog->type){+/* TODO: add LSM hooks */+default:+result=-EINVAL;+gotoput_prog;+}++/* validate allocated memory */+prog_list_pages=prog->pages;+for(cp_walker=current->seccomp.landlock_prog;cp_walker;+cp_walker=cp_walker->prev){+/* TODO: add penalty for each prog? */+prog_list_pages+=cp_walker->prog->pages;+}+if(prog_list_pages>LANDLOCK_PROG_LIST_MAX_PAGES){+result=-ENOMEM;+gotoput_prog;+}++landlock_prog=kmalloc(sizeof(*landlock_prog),GFP_KERNEL);+if(!landlock_prog){+result=-ENOMEM;+gotoput_prog;+}+landlock_prog->prog=prog;+landlock_prog->filter=current->seccomp.filter;+if(landlock_prog->filter)+atomic_inc(&landlock_prog->filter->usage);+atomic_set(&landlock_prog->usage,1);+landlock_prog->prev=current->seccomp.landlock_prog;+current->seccomp.landlock_prog=landlock_prog;+return0;++put_prog:+bpf_prog_put(prog);+returnresult;+}+#endif /* CONFIG_SECURITY_LANDLOCK */+/* Common entry point for both prctl and syscall. */staticlongdo_seccomp(unsignedintop,unsignedintflags,constchar__user*uargs)
@@ -825,6 +1009,10 @@ static long do_seccomp(unsigned int op, unsigned int flags,returnseccomp_set_mode_strict();caseSECCOMP_SET_MODE_FILTER:returnseccomp_set_mode_filter(flags,uargs);+#ifdef CONFIG_SECURITY_LANDLOCK+caseSECCOMP_SET_LANDLOCK_HOOK:+returnlandlock_set_hook(flags,uargs);+#endif /* CONFIG_SECURITY_LANDLOCK */default:return-EINVAL;}
@@ -35,5 +35,6 @@#define EPIPE 32 /* Broken pipe */#define EDOM 33 /* Math argument out of domain of func */#define ERANGE 34 /* Math result not representable */+#define _ERRNO_LAST ERANGE
At the very least this needs a more sensible name.
From: Andy Lutomirski <luto@amacapital.net> Date: 2016-08-25 11:16:53
On Thu, Aug 25, 2016 at 3:32 AM, Mickaël Salaün [off-list ref] wrote:
Add an eBPF function bpf_landlock_cmp_cgroup_beneath(opt, map, map_op)
to compare the current process cgroup with a cgroup handle, The handle
can match the current cgroup if it is the same or a child. This allows
to make conditional rules according to the current cgroup.
A cgroup handle is a map entry created from a file descriptor referring
a cgroup directory (e.g. by opening /sys/fs/cgroup/X). In this case, the
map entry is of type BPF_MAP_HANDLE_TYPE_LANDLOCK_CGROUP_FD and the
inferred array map is of type BPF_MAP_ARRAY_TYPE_LANDLOCK_CGROUP.
Can you elaborate on why this is useful? I.e. why not just supply
different policies to different subtrees.
Also, how does this interact with the current cgroup v1 vs v2 mess?
As far as I can tell, no one can even really agree on what "what
cgroup am I in" means right now.
An unprivileged process can create and manipulate cgroups thanks to
cgroup delegation.
From: Andy Lutomirski <luto@amacapital.net> Date: 2016-08-25 11:19:26
On Thu, Aug 25, 2016 at 3:32 AM, Mickaël Salaün [off-list ref] wrote:
Add eBPF functions to compare file system access with a Landlock file
system handle:
* bpf_landlock_cmp_fs_prop_with_struct_file(prop, map, map_op, file)
This function allows to compare the dentry, inode, device or mount
point of the currently accessed file, with a reference handle.
* bpf_landlock_cmp_fs_beneath_with_struct_file(opt, map, map_op, file)
This function allows an eBPF program to check if the current accessed
file is the same or in the hierarchy of a reference handle.
The goal of file system handle is to abstract kernel objects such as a
struct file or a struct inode. Userland can create this kind of handle
thanks to the BPF_MAP_UPDATE_ELEM command. The element is a struct
landlock_handle containing the handle type (e.g.
BPF_MAP_HANDLE_TYPE_LANDLOCK_FS_FD) and a file descriptor. This could
also be any descriptions able to match a struct file or a struct inode
(e.g. path or glob string).
This needs Eric's opinion.
Also, where do all the struct file *'s get stashed? Are they
preserved in the arraymap? What prevents reference cycles or absurdly
large numbers of struct files getting pinned?
--Andy
From: Andy Lutomirski <luto@amacapital.net> Date: 2016-08-25 11:28:30
On Thu, Aug 25, 2016 at 3:32 AM, Mickaël Salaün [off-list ref] wrote:
Hi,
This series is a proof of concept to fill some missing part of seccomp as the
ability to check syscall argument pointers or creating more dynamic security
policies. The goal of this new stackable Linux Security Module (LSM) called
Landlock is to allow any process, including unprivileged ones, to create
powerful security sandboxes comparable to the Seatbelt/XNU Sandbox or the
OpenBSD Pledge. This kind of sandbox help to mitigate the security impact of
bugs or unexpected/malicious behaviors in userland applications.
Maybe I'm missing an obvious description, but: do you have a
description of the eBPF API to landlock? What function do you
provide, when is it called, what functions can it call, what does the
fancy new arraymap do, etc?
--Andy
On Thu, Aug 25, 2016 at 3:32 AM, Mickaël Salaün [off-list ref] wrote:
quoted
Hi,
This series is a proof of concept to fill some missing part of seccomp as the
ability to check syscall argument pointers or creating more dynamic security
policies. The goal of this new stackable Linux Security Module (LSM) called
Landlock is to allow any process, including unprivileged ones, to create
powerful security sandboxes comparable to the Seatbelt/XNU Sandbox or the
OpenBSD Pledge. This kind of sandbox help to mitigate the security impact of
bugs or unexpected/malicious behaviors in userland applications.
Maybe I'm missing an obvious description, but: do you have a
description of the eBPF API to landlock? What function do you
provide, when is it called, what functions can it call, what does the
fancy new arraymap do, etc?
--Andy
The eBPF context is described in "[RFC v2 06/10] landlock: Add LSM hooks".
The provided eBPF functions are described in "[RFC v2 08/10] landlock:
Handle file system comparisons"
(bpf_landlock_cmp_fs_prop_with_struct_file and
bpf_landlock_cmp_fs_beneath_with_struct_file) and "[RFC v2 09/10]
landlock: Handle cgroups" (bpf_landlock_cmp_cgroup_beneath). The
function descriptions are summarized in include/uapi/linux/bpf.h .
This functions can be called by an eBPF program of type
BPF_PROG_TYPE_LANDLOCK_FILE_OPEN, BPF_PROG_TYPE_LANDLOCK_FILE_PERMISSION
and BPF_PROG_TYPE_LANDLOCK_MMAP_FILE as described in "[RFC v2 06/10]
landlock: Add LSM hooks".
I tried to split the commits as much as possible to ease the review. The
"[RFC v2 10/10] samples/landlock: Add sandbox example" may help to see
the whole picture.
Hope this helps,
Mickaël
On Thu, Aug 25, 2016 at 3:32 AM, Mickaël Salaün [off-list ref] wrote:
quoted
Add eBPF functions to compare file system access with a Landlock file
system handle:
* bpf_landlock_cmp_fs_prop_with_struct_file(prop, map, map_op, file)
This function allows to compare the dentry, inode, device or mount
point of the currently accessed file, with a reference handle.
* bpf_landlock_cmp_fs_beneath_with_struct_file(opt, map, map_op, file)
This function allows an eBPF program to check if the current accessed
file is the same or in the hierarchy of a reference handle.
The goal of file system handle is to abstract kernel objects such as a
struct file or a struct inode. Userland can create this kind of handle
thanks to the BPF_MAP_UPDATE_ELEM command. The element is a struct
landlock_handle containing the handle type (e.g.
BPF_MAP_HANDLE_TYPE_LANDLOCK_FS_FD) and a file descriptor. This could
also be any descriptions able to match a struct file or a struct inode
(e.g. path or glob string).
This needs Eric's opinion.
Also, where do all the struct file *'s get stashed? Are they
preserved in the arraymap? What prevents reference cycles or absurdly
large numbers of struct files getting pinned?
Yes, the struct file are kept in the arraymap and dropped when there is
no more reference on them. Currently, the limitations are the maximum
number of open file descriptors referring to an arraymap and the maximum
number of eBPF Landlock programs loaded in a process
(LANDLOCK_PROG_LIST_MAX_PAGES in kernel/seccomp.c).
What kind of reference cycles have you in mind?
It probably needs another limit for kernel object references as well.
What is the best option here? Add another static limitation or use an
existing one?
Mickaël
On Thu, Aug 25, 2016 at 3:32 AM, Mickaël Salaün [off-list ref] wrote:
quoted
Add an eBPF function bpf_landlock_cmp_cgroup_beneath(opt, map, map_op)
to compare the current process cgroup with a cgroup handle, The handle
can match the current cgroup if it is the same or a child. This allows
to make conditional rules according to the current cgroup.
A cgroup handle is a map entry created from a file descriptor referring
a cgroup directory (e.g. by opening /sys/fs/cgroup/X). In this case, the
map entry is of type BPF_MAP_HANDLE_TYPE_LANDLOCK_CGROUP_FD and the
inferred array map is of type BPF_MAP_ARRAY_TYPE_LANDLOCK_CGROUP.
Can you elaborate on why this is useful? I.e. why not just supply
different policies to different subtrees.
The main use case I see is to load the security policies at the start of
a user session for all processes but not enforce them right away. The
user can then keep a shell for Landlock administration tasks and lock
the other processes with a dedicated cgroup on the fly. This allows the
user to make unremovable Landlock security policies but only activate
them when needed for specific processes.
Also, how does this interact with the current cgroup v1 vs v2 mess?
As far as I can tell, no one can even really agree on what "what
cgroup am I in" means right now.
I tested with cgroup-v2 but indeed, it seems a bit different with
cgroup-v1 :)
Does anyone know how to handle both cases?
quoted
An unprivileged process can create and manipulate cgroups thanks to
cgroup delegation.
What is cgroup delegation?
This is simply the action of changing the owner of cgroup sysfs files to
allow an unprivileged user to handle them (cf. Documentation/cgroup-v2.txt)
Mickaël
On Thu, Aug 25, 2016 at 12:32:44PM +0200, Mickaël Salaün wrote:
Add an eBPF function bpf_landlock_cmp_cgroup_beneath(opt, map, map_op)
to compare the current process cgroup with a cgroup handle, The handle
can match the current cgroup if it is the same or a child. This allows
to make conditional rules according to the current cgroup.
A cgroup handle is a map entry created from a file descriptor referring
a cgroup directory (e.g. by opening /sys/fs/cgroup/X). In this case, the
map entry is of type BPF_MAP_HANDLE_TYPE_LANDLOCK_CGROUP_FD and the
inferred array map is of type BPF_MAP_ARRAY_TYPE_LANDLOCK_CGROUP.
An unprivileged process can create and manipulate cgroups thanks to
cgroup delegation.
Signed-off-by: Mickaël Salaün <mic@digikod.net>
...
+static inline u64 bpf_landlock_cmp_cgroup_beneath(u64 r1_option, u64 r2_map,
+ u64 r3_map_op, u64 r4, u64 r5)
+{
+ u8 option = (u8) r1_option;
+ struct bpf_map *map = (struct bpf_map *) (unsigned long) r2_map;
+ enum bpf_map_array_op map_op = r3_map_op;
+ struct bpf_array *array = container_of(map, struct bpf_array, map);
+ struct cgroup *cg1, *cg2;
+ struct map_landlock_handle *handle;
+ int i;
+
+ /* ARG_CONST_PTR_TO_LANDLOCK_HANDLE_CGROUP is an arraymap */
+ if (unlikely(!map)) {
+ WARN_ON(1);
+ return -EFAULT;
+ }
+ if (unlikely((option | _LANDLOCK_FLAG_OPT_MASK) != _LANDLOCK_FLAG_OPT_MASK))
+ return -EINVAL;
+
+ /* for now, only handle OP_OR */
+ switch (map_op) {
+ case BPF_MAP_ARRAY_OP_OR:
+ break;
+ case BPF_MAP_ARRAY_OP_UNSPEC:
+ case BPF_MAP_ARRAY_OP_AND:
+ case BPF_MAP_ARRAY_OP_XOR:
+ default:
+ return -EINVAL;
+ }
+
+ synchronize_rcu();
+
+ for (i = 0; i < array->n_entries; i++) {
+ handle = (struct map_landlock_handle *)
+ (array->value + array->elem_size * i);
+
+ /* protected by the proto types, should not happen */
+ if (unlikely(handle->type != BPF_MAP_HANDLE_TYPE_LANDLOCK_CGROUP_FD)) {
+ WARN_ON(1);
+ return -EFAULT;
+ }
+ if (unlikely(!handle->css)) {
+ WARN_ON(1);
+ return -EFAULT;
+ }
+
+ if (option & LANDLOCK_FLAG_OPT_REVERSE) {
+ cg1 = handle->css->cgroup;
+ cg2 = task_css_set(current)->dfl_cgrp;
+ } else {
+ cg1 = task_css_set(current)->dfl_cgrp;
+ cg2 = handle->css->cgroup;
+ }
+
+ if (cgroup_is_descendant(cg1, cg2))
+ return 0;
+ }
+ return 1;
+}
- please take a loook at exisiting bpf_current_task_under_cgroup and
reuse BPF_MAP_TYPE_CGROUP_ARRAY as a minimum. Doing new cgroup array
is nothing but duplication of the code.
- I don't think such 'for' loop can scale. The solution needs to work
with thousands of containers and thousands of cgroups.
In the patch 06/10 the proposal is to use 'current' as holder of
the programs:
+ for (prog = current->seccomp.landlock_prog;
+ prog; prog = prog->prev) {
+ if (prog->filter == landlock_ret->filter) {
+ cur_ret = BPF_PROG_RUN(prog->prog, (void *)&ctx);
+ break;
+ }
+ }
imo that's the root of scalability issue.
I think to be able to scale the bpf programs have to be attached to
cgroups instead of tasks.
That would be very different api. seccomp doesn't need to be touched.
But that is the only way I see to be able to scale.
May be another way of thinking about it is 'lsm cgroup controller'
that Sargun is proposing.
The lsm hooks will provide stable execution points and the programs
will be called like:
prog = task_css_set(current)->dfl_cgrp->bpf.prog_effective[lsm_hook_id];
BPF_PROG_RUN(prog, ctx);
The delegation functionality and 'prog_effective' logic that
Daniel Mack is proposing will be fully reused here.
External container management software will be able to apply bpf
programs to control tasks under cgroup and such
bpf_landlock_cmp_cgroup_beneath() helper won't be necessary.
The user will be able to register different programs for different lsm hooks.
If I understand the patch 6/10 correctly, there is one (or a list) prog for
all lsm hooks per task which is not flexible enough.
Anoop Naravaram's use case is to control the ports the applications
under cgroup can bind and listen on.
Such use case can be solved by such 'lsm cgroup controller' by
attaching bpf program to security_socket_bind lsm hook and
filtering sockaddr.
Furthermore Sargun's use case is to allow further sockaddr rewrites
from the bpf program which can be done as natural extension
of such mechanism.
If I understood Daniel's Anoop's Sargun's and yours use cases
correctly the common piece of kernel infrastructure that can solve
them all can start from Daniel's current set of patches that
establish a mechanism of attaching bpf program to a cgroup.
Then adding lsm hooks to it and later allowing argument rewrite
(since they're already in the kernel and no ToCToU problems exist)
As far as safety and type checking that bpf programs has to do,
I like the approach of patch 06/10:
+LANDLOCK_HOOK2(file_open, FILE_OPEN,
+ PTR_TO_STRUCT_FILE, struct file *, file,
+ PTR_TO_STRUCT_CRED, const struct cred *, cred
+)
teaching verifier to recognize struct file, cred, sockaddr
will let bpf program access them naturally without any overhead.
Though:
is a bit of overkill.
I think it would be cleaner to have single
BPF_PROG_TYPE_LSM and at program load time pass
lsm_hook_id as well, so that verifier can do safety checks
based on type info provided in LANDLOCK_HOOKs
Hello,
On Thu, Aug 25, 2016 at 04:44:13PM +0200, Mickaël Salaün wrote:
I tested with cgroup-v2 but indeed, it seems a bit different with
cgroup-v1 :)
Does anyone know how to handle both cases?
If you wanna do cgroup membership test, just do cgroup v2 membership
test. No need to introduce a new controller and possibly struct sock
association field for that. That's what all new cgroup aware network
operations are using anyway and doesn't conflicts with whether other
controllers are v1 or v2.
For examples of using cgroup v2 membership test, please take a look at
cgroup_mt_v1().
Thanks.
--
tejun
From: Andy Lutomirski <luto@amacapital.net> Date: 2016-08-26 14:21:02
On Thu, Aug 25, 2016 at 7:44 AM, Mickaël Salaün [off-list ref] wrote:
On 25/08/2016 13:09, Andy Lutomirski wrote:
quoted
On Thu, Aug 25, 2016 at 3:32 AM, Mickaël Salaün [off-list ref] wrote:
quoted
Add an eBPF function bpf_landlock_cmp_cgroup_beneath(opt, map, map_op)
to compare the current process cgroup with a cgroup handle, The handle
can match the current cgroup if it is the same or a child. This allows
to make conditional rules according to the current cgroup.
A cgroup handle is a map entry created from a file descriptor referring
a cgroup directory (e.g. by opening /sys/fs/cgroup/X). In this case, the
map entry is of type BPF_MAP_HANDLE_TYPE_LANDLOCK_CGROUP_FD and the
inferred array map is of type BPF_MAP_ARRAY_TYPE_LANDLOCK_CGROUP.
Can you elaborate on why this is useful? I.e. why not just supply
different policies to different subtrees.
The main use case I see is to load the security policies at the start of
a user session for all processes but not enforce them right away. The
user can then keep a shell for Landlock administration tasks and lock
the other processes with a dedicated cgroup on the fly. This allows the
user to make unremovable Landlock security policies but only activate
them when needed for specific processes.
This seems like a bit of a dubious use case to me. The landlock
mechanism should be flexible enough to do this kind of thing even
without cgroups, and "spawn a process, wait a while, and then confine
it by fiddling with cgroups" seems a lot dicier than just loading the
right policy in the first place, especially since eBPF policies can be
stateful.
quoted
Also, how does this interact with the current cgroup v1 vs v2 mess?
As far as I can tell, no one can even really agree on what "what
cgroup am I in" means right now.
I tested with cgroup-v2 but indeed, it seems a bit different with
cgroup-v1 :)
Does anyone know how to handle both cases?
quoted
quoted
An unprivileged process can create and manipulate cgroups thanks to
cgroup delegation.
What is cgroup delegation?
This is simply the action of changing the owner of cgroup sysfs files to
allow an unprivileged user to handle them (cf. Documentation/cgroup-v2.txt)
As far as I can tell, Tejun and systemd both actively discourage doing
this. Maybe I misunderstand. But in any event, the admin giving you
a cgroup hierarchy you can use for this means that the admin has to
cooperate with your policy, and it further requires (with cgroup v2 or
similar, which is most likely the future) that your lockdown policy be
compatible with your resource control policy.
I would suggest dropping this lockdown feature until a use case
emerges that really can't be addressed adequately without it.
--Andy
From: Andy Lutomirski <luto@amacapital.net> Date: 2016-08-26 14:57:49
On Thu, Aug 25, 2016 at 7:10 AM, Mickaël Salaün [off-list ref] wrote:
On 25/08/2016 13:12, Andy Lutomirski wrote:
quoted
On Thu, Aug 25, 2016 at 3:32 AM, Mickaël Salaün [off-list ref] wrote:
quoted
Add eBPF functions to compare file system access with a Landlock file
system handle:
* bpf_landlock_cmp_fs_prop_with_struct_file(prop, map, map_op, file)
This function allows to compare the dentry, inode, device or mount
point of the currently accessed file, with a reference handle.
* bpf_landlock_cmp_fs_beneath_with_struct_file(opt, map, map_op, file)
This function allows an eBPF program to check if the current accessed
file is the same or in the hierarchy of a reference handle.
The goal of file system handle is to abstract kernel objects such as a
struct file or a struct inode. Userland can create this kind of handle
thanks to the BPF_MAP_UPDATE_ELEM command. The element is a struct
landlock_handle containing the handle type (e.g.
BPF_MAP_HANDLE_TYPE_LANDLOCK_FS_FD) and a file descriptor. This could
also be any descriptions able to match a struct file or a struct inode
(e.g. path or glob string).
This needs Eric's opinion.
Also, where do all the struct file *'s get stashed? Are they
preserved in the arraymap? What prevents reference cycles or absurdly
large numbers of struct files getting pinned?
Yes, the struct file are kept in the arraymap and dropped when there is
no more reference on them. Currently, the limitations are the maximum
number of open file descriptors referring to an arraymap and the maximum
number of eBPF Landlock programs loaded in a process
(LANDLOCK_PROG_LIST_MAX_PAGES in kernel/seccomp.c).
What kind of reference cycles have you in mind?
Shoving evil things into the arraymaps, e.g. unix sockets with
SCM_RIGHTS messages pending, eBPF program references, the arraymap fd
itself, another arraymap fd, etc.
It probably needs another limit for kernel object references as well.
What is the best option here? Add another static limitation or use an
existing one?
Dunno. If RLIMIT_FILE could be made to work, that would be nice.
--Andy
On Thu, Aug 25, 2016 at 12:32:44PM +0200, Mickaël Salaün wrote:
quoted
Add an eBPF function bpf_landlock_cmp_cgroup_beneath(opt, map, map_op)
to compare the current process cgroup with a cgroup handle, The handle
can match the current cgroup if it is the same or a child. This allows
to make conditional rules according to the current cgroup.
A cgroup handle is a map entry created from a file descriptor referring
a cgroup directory (e.g. by opening /sys/fs/cgroup/X). In this case, the
map entry is of type BPF_MAP_HANDLE_TYPE_LANDLOCK_CGROUP_FD and the
inferred array map is of type BPF_MAP_ARRAY_TYPE_LANDLOCK_CGROUP.
An unprivileged process can create and manipulate cgroups thanks to
cgroup delegation.
Signed-off-by: Mickaël Salaün <mic@digikod.net>
...
quoted
+static inline u64 bpf_landlock_cmp_cgroup_beneath(u64 r1_option, u64 r2_map,
+ u64 r3_map_op, u64 r4, u64 r5)
+{
+ u8 option = (u8) r1_option;
+ struct bpf_map *map = (struct bpf_map *) (unsigned long) r2_map;
+ enum bpf_map_array_op map_op = r3_map_op;
+ struct bpf_array *array = container_of(map, struct bpf_array, map);
+ struct cgroup *cg1, *cg2;
+ struct map_landlock_handle *handle;
+ int i;
+
+ /* ARG_CONST_PTR_TO_LANDLOCK_HANDLE_CGROUP is an arraymap */
+ if (unlikely(!map)) {
+ WARN_ON(1);
+ return -EFAULT;
+ }
+ if (unlikely((option | _LANDLOCK_FLAG_OPT_MASK) != _LANDLOCK_FLAG_OPT_MASK))
+ return -EINVAL;
+
+ /* for now, only handle OP_OR */
+ switch (map_op) {
+ case BPF_MAP_ARRAY_OP_OR:
+ break;
+ case BPF_MAP_ARRAY_OP_UNSPEC:
+ case BPF_MAP_ARRAY_OP_AND:
+ case BPF_MAP_ARRAY_OP_XOR:
+ default:
+ return -EINVAL;
+ }
+
+ synchronize_rcu();
+
+ for (i = 0; i < array->n_entries; i++) {
+ handle = (struct map_landlock_handle *)
+ (array->value + array->elem_size * i);
+
+ /* protected by the proto types, should not happen */
+ if (unlikely(handle->type != BPF_MAP_HANDLE_TYPE_LANDLOCK_CGROUP_FD)) {
+ WARN_ON(1);
+ return -EFAULT;
+ }
+ if (unlikely(!handle->css)) {
+ WARN_ON(1);
+ return -EFAULT;
+ }
+
+ if (option & LANDLOCK_FLAG_OPT_REVERSE) {
+ cg1 = handle->css->cgroup;
+ cg2 = task_css_set(current)->dfl_cgrp;
+ } else {
+ cg1 = task_css_set(current)->dfl_cgrp;
+ cg2 = handle->css->cgroup;
+ }
+
+ if (cgroup_is_descendant(cg1, cg2))
+ return 0;
+ }
+ return 1;
+}
- please take a loook at exisiting bpf_current_task_under_cgroup and
reuse BPF_MAP_TYPE_CGROUP_ARRAY as a minimum. Doing new cgroup array
is nothing but duplication of the code.
Oh, I didn't know about this patchset and the new helper. Indeed, it
looks a lot like mine except there is no static verification of the map
type as I did with the arraymap of handles, and no batch mode either. I
think the return value of bpf_current_task_under_cgroup is error-prone
if an eBPF program do an "if(ret)" test on the value (because of the
negative ERRNO return value). Inverting the 0 and 1 return values should
fix this (0 == succeed, 1 == failed, <0 == error).
To sum up, there is four related patchsets:
* "Landlock LSM: Unprivileged sandboxing" (this series)
* "Add Checmate, BPF-driven minor LSM" (Sargun Dhillon)
* "Networking cgroup controller" (Anoop Naravaram)
* "Add eBPF hooks for cgroups" (Daniel Mack)
The three other series (Sargun's, Anoop's and Daniel's) are mainly
focused on network access-control via cgroup for *containers*. As far as
I can tell, only a *root* user (CAP_SYS_ADMIN) can use them. Landlock's
goal is to empower all processes (privileged or not) to create their own
sandbox. This also means, like explained in "[RFC v2 00/10] Landlock
LSM: Unprivileged sandboxing", there is more constraints. For example,
it is not acceptable to let a process probe the kernel memory as it
wish. More details are in the Landlock cover-letter.
Another important point is that supporting cgroup for Landlock is
optional. It does not rely on cgroup to be usable but is only a feature
available when (unprivileged) users can manage there own cgroup, which
is an important constraint. Put another way, Landlock should not rely on
cgroup to create sandboxes. Indeed, a process creating a sandbox do not
necessarily have access to the cgroup mount point (directly or not).
- I don't think such 'for' loop can scale. The solution needs to work
with thousands of containers and thousands of cgroups.
In the patch 06/10 the proposal is to use 'current' as holder of
the programs:
+ for (prog = current->seccomp.landlock_prog;
+ prog; prog = prog->prev) {
+ if (prog->filter == landlock_ret->filter) {
+ cur_ret = BPF_PROG_RUN(prog->prog, (void *)&ctx);
+ break;
+ }
+ }
imo that's the root of scalability issue.
I think to be able to scale the bpf programs have to be attached to
cgroups instead of tasks.
That would be very different api. seccomp doesn't need to be touched.
But that is the only way I see to be able to scale.
Landlock is inspired from seccomp which also use a BPF program per
thread. For seccomp, each BPF programs are executed for each syscall.
For Landlock, some BPF programs are executed for some LSM hooks. I don't
see why it is a scale issue for Landlock comparing to seccomp. I also
don't see why storing the BPF program list pointer in the cgroup struct
instead of the task struct change a lot here. The BPF programs execution
will be the same anyway (for each LSM hook). Kees should probably have a
better opinion on this.
May be another way of thinking about it is 'lsm cgroup controller'
that Sargun is proposing.
The lsm hooks will provide stable execution points and the programs
will be called like:
prog = task_css_set(current)->dfl_cgrp->bpf.prog_effective[lsm_hook_id];
BPF_PROG_RUN(prog, ctx);
The delegation functionality and 'prog_effective' logic that
Daniel Mack is proposing will be fully reused here.
External container management software will be able to apply bpf
programs to control tasks under cgroup and such
bpf_landlock_cmp_cgroup_beneath() helper won't be necessary.
The user will be able to register different programs for different lsm hooks.
If I understand the patch 6/10 correctly, there is one (or a list) prog for
all lsm hooks per task which is not flexible enough.
For each LSM hook triggered by a thread, all of its Landlock eBPF
programs (dedicated for this kind of hook) will be evaluated (if
needed). This is the same behavior as seccomp (list of BPF programs
attached to a process hierarchy) except the BPF programs are not
evaluated for syscall but for LSM hooks. There is no way to make it more
fine-grained :)
Anoop Naravaram's use case is to control the ports the applications
under cgroup can bind and listen on.
Such use case can be solved by such 'lsm cgroup controller' by
attaching bpf program to security_socket_bind lsm hook and
filtering sockaddr.
Furthermore Sargun's use case is to allow further sockaddr rewrites
from the bpf program which can be done as natural extension
of such mechanism.
If I understood Daniel's Anoop's Sargun's and yours use cases
correctly the common piece of kernel infrastructure that can solve
them all can start from Daniel's current set of patches that
establish a mechanism of attaching bpf program to a cgroup.
Then adding lsm hooks to it and later allowing argument rewrite
(since they're already in the kernel and no ToCToU problems exist)
To sum up, the pieces we have in common are the eBPF use and (optionally
for Landlock) there execution depending of the current cgroup.
Moreover, the three other series (Sargun's, Anoop's and Daniel's) do not
deal with unprivileged process which is the main purpose of Landlock.
I'm not sure that allowing sockaddr rewrites is a good idea here... Like
other LSMs, Landlock is dedicated to access-control.
For the network-related series, I think it make more sense to simply
create a netfilter rule matching a cgroup and then add more features to
netfilter (restrict port ranges and so on) thanks to eBPF programs.
Containers are (usually) in a dedicated network namespace, which open
the possibility to not only rely on cgroups (e.g. match UID,
netmask...). It would also be more flexible to be able to load a BPF
program in netfilter and update its maps on the fly to make dynamic
rules, like ipset does, but in a more generic way.
quoted hunk
As far as safety and type checking that bpf programs has to do,
I like the approach of patch 06/10:
+LANDLOCK_HOOK2(file_open, FILE_OPEN,
+ PTR_TO_STRUCT_FILE, struct file *, file,
+ PTR_TO_STRUCT_CRED, const struct cred *, cred
+)
teaching verifier to recognize struct file, cred, sockaddr
will let bpf program access them naturally without any overhead.
Though:
is a bit of overkill.
I think it would be cleaner to have single
BPF_PROG_TYPE_LSM and at program load time pass
lsm_hook_id as well, so that verifier can do safety checks
based on type info provided in LANDLOCK_HOOKs
I first started with a unique BPF_PROG_TYPE but, the thing is, the BPF
verifier check programs according to their types. If we need to check
specific context value types (e.g. PTR_TO_STRUCT_FILE), we need a
dedicated program types. I don't see any other way to do it with the
current verifier code. Moreover it's the purpose of program types, right?
Regards,
Mickaël
Hello,
On Fri, Aug 26, 2016 at 07:20:35AM -0700, Andy Lutomirski wrote:
quoted
This is simply the action of changing the owner of cgroup sysfs files to
allow an unprivileged user to handle them (cf. Documentation/cgroup-v2.txt)
As far as I can tell, Tejun and systemd both actively discourage doing
this. Maybe I misunderstand. But in any event, the admin giving you
Please refer to "2-5. Delegation" of Documentation/cgroup-v2.txt.
Delegation on v1 is broken on both core and specific controller
behaviors and thus discouraged. On v2, delegation should work just
fine.
I haven't looked in detail but in general I'm not too excited about
layering security mechanism on top of cgroup. Maybe it makes some
sense when security domain coincides with resource domains but at any
rate please keep me in the loop.
Thanks.
--
tejun
On Fri, Aug 26, 2016 at 05:10:40PM +0200, Mickaël Salaün wrote:
trimming cc list again. When it's too big vger will consider it as spam.
On 26/08/2016 04:14, Alexei Starovoitov wrote:
quoted
On Thu, Aug 25, 2016 at 12:32:44PM +0200, Mickaël Salaün wrote:
quoted
Add an eBPF function bpf_landlock_cmp_cgroup_beneath(opt, map, map_op)
to compare the current process cgroup with a cgroup handle, The handle
can match the current cgroup if it is the same or a child. This allows
to make conditional rules according to the current cgroup.
A cgroup handle is a map entry created from a file descriptor referring
a cgroup directory (e.g. by opening /sys/fs/cgroup/X). In this case, the
map entry is of type BPF_MAP_HANDLE_TYPE_LANDLOCK_CGROUP_FD and the
inferred array map is of type BPF_MAP_ARRAY_TYPE_LANDLOCK_CGROUP.
An unprivileged process can create and manipulate cgroups thanks to
cgroup delegation.
Signed-off-by: Mickaël Salaün <mic@digikod.net>
...
quoted
+static inline u64 bpf_landlock_cmp_cgroup_beneath(u64 r1_option, u64 r2_map,
+ u64 r3_map_op, u64 r4, u64 r5)
+{
+ u8 option = (u8) r1_option;
+ struct bpf_map *map = (struct bpf_map *) (unsigned long) r2_map;
+ enum bpf_map_array_op map_op = r3_map_op;
+ struct bpf_array *array = container_of(map, struct bpf_array, map);
+ struct cgroup *cg1, *cg2;
+ struct map_landlock_handle *handle;
+ int i;
+
+ /* ARG_CONST_PTR_TO_LANDLOCK_HANDLE_CGROUP is an arraymap */
+ if (unlikely(!map)) {
+ WARN_ON(1);
+ return -EFAULT;
+ }
+ if (unlikely((option | _LANDLOCK_FLAG_OPT_MASK) != _LANDLOCK_FLAG_OPT_MASK))
+ return -EINVAL;
+
+ /* for now, only handle OP_OR */
+ switch (map_op) {
+ case BPF_MAP_ARRAY_OP_OR:
+ break;
+ case BPF_MAP_ARRAY_OP_UNSPEC:
+ case BPF_MAP_ARRAY_OP_AND:
+ case BPF_MAP_ARRAY_OP_XOR:
+ default:
+ return -EINVAL;
+ }
+
+ synchronize_rcu();
+
+ for (i = 0; i < array->n_entries; i++) {
+ handle = (struct map_landlock_handle *)
+ (array->value + array->elem_size * i);
+
+ /* protected by the proto types, should not happen */
+ if (unlikely(handle->type != BPF_MAP_HANDLE_TYPE_LANDLOCK_CGROUP_FD)) {
+ WARN_ON(1);
+ return -EFAULT;
+ }
+ if (unlikely(!handle->css)) {
+ WARN_ON(1);
+ return -EFAULT;
+ }
+
+ if (option & LANDLOCK_FLAG_OPT_REVERSE) {
+ cg1 = handle->css->cgroup;
+ cg2 = task_css_set(current)->dfl_cgrp;
+ } else {
+ cg1 = task_css_set(current)->dfl_cgrp;
+ cg2 = handle->css->cgroup;
+ }
+
+ if (cgroup_is_descendant(cg1, cg2))
+ return 0;
+ }
+ return 1;
+}
- please take a loook at exisiting bpf_current_task_under_cgroup and
reuse BPF_MAP_TYPE_CGROUP_ARRAY as a minimum. Doing new cgroup array
is nothing but duplication of the code.
Oh, I didn't know about this patchset and the new helper. Indeed, it
looks a lot like mine except there is no static verification of the map
type as I did with the arraymap of handles, and no batch mode either. I
think the return value of bpf_current_task_under_cgroup is error-prone
if an eBPF program do an "if(ret)" test on the value (because of the
negative ERRNO return value). Inverting the 0 and 1 return values should
fix this (0 == succeed, 1 == failed, <0 == error).
nothing to fix. It's good as-is. Use if (ret > 0) instead.
To sum up, there is four related patchsets:
* "Landlock LSM: Unprivileged sandboxing" (this series)
* "Add Checmate, BPF-driven minor LSM" (Sargun Dhillon)
* "Networking cgroup controller" (Anoop Naravaram)
* "Add eBPF hooks for cgroups" (Daniel Mack)
The three other series (Sargun's, Anoop's and Daniel's) are mainly
focused on network access-control via cgroup for *containers*. As far as
I can tell, only a *root* user (CAP_SYS_ADMIN) can use them. Landlock's
goal is to empower all processes (privileged or not) to create their own
sandbox. This also means, like explained in "[RFC v2 00/10] Landlock
LSM: Unprivileged sandboxing", there is more constraints. For example,
it is not acceptable to let a process probe the kernel memory as it
wish. More details are in the Landlock cover-letter.
Another important point is that supporting cgroup for Landlock is
optional. It does not rely on cgroup to be usable but is only a feature
available when (unprivileged) users can manage there own cgroup, which
is an important constraint. Put another way, Landlock should not rely on
cgroup to create sandboxes. Indeed, a process creating a sandbox do not
necessarily have access to the cgroup mount point (directly or not).
cgroup is the common way to group multiple tasks.
Without cgroup only parent<->child relationship will be possible,
which will limit usability of such lsm to a master task that controls
its children. Such api restriction would have been ok, if we could
extend it in the future, but unfortunately task-centric won't allow it
without creating a parallel lsm that is cgroup based.
Therefore I think we have to go with cgroup-centric api and your
application has to use cgroups from the start though only parent-child
would have been enough.
Also I don't think the kernel can afford two bpf based lsm. One task
based and another cgroup based, so we have to find common ground
that suits both use cases.
Having unprivliged access is a subset. There is no strong reason why
cgroup+lsm+bpf should be limited to root only always.
When we can guarantee no pointer leaks, we can allow unpriv.
quoted
- I don't think such 'for' loop can scale. The solution needs to work
with thousands of containers and thousands of cgroups.
In the patch 06/10 the proposal is to use 'current' as holder of
the programs:
+ for (prog = current->seccomp.landlock_prog;
+ prog; prog = prog->prev) {
+ if (prog->filter == landlock_ret->filter) {
+ cur_ret = BPF_PROG_RUN(prog->prog, (void *)&ctx);
+ break;
+ }
+ }
imo that's the root of scalability issue.
I think to be able to scale the bpf programs have to be attached to
cgroups instead of tasks.
That would be very different api. seccomp doesn't need to be touched.
But that is the only way I see to be able to scale.
Landlock is inspired from seccomp which also use a BPF program per
thread. For seccomp, each BPF programs are executed for each syscall.
For Landlock, some BPF programs are executed for some LSM hooks. I don't
see why it is a scale issue for Landlock comparing to seccomp. I also
don't see why storing the BPF program list pointer in the cgroup struct
instead of the task struct change a lot here. The BPF programs execution
will be the same anyway (for each LSM hook). Kees should probably have a
better opinion on this.
seccomp has its own issues and copying them doesn't make this lsm any better.
Like seccomp bpf programs are all gigantic switch statement that looks
for interesting syscall numbers. All syscalls of a task are paying
non-trivial seccomp penalty due to such design. If bpf was attached per
syscall it would have been much cheaper. Of course doing it this way
for seccomp is not easy, but for lsm such facility is already there.
Blank call of a single bpf prog for all lsm hooks is unnecessary
overhead that can and should be avoided.
quoted
May be another way of thinking about it is 'lsm cgroup controller'
that Sargun is proposing.
The lsm hooks will provide stable execution points and the programs
will be called like:
prog = task_css_set(current)->dfl_cgrp->bpf.prog_effective[lsm_hook_id];
BPF_PROG_RUN(prog, ctx);
The delegation functionality and 'prog_effective' logic that
Daniel Mack is proposing will be fully reused here.
External container management software will be able to apply bpf
programs to control tasks under cgroup and such
bpf_landlock_cmp_cgroup_beneath() helper won't be necessary.
The user will be able to register different programs for different lsm hooks.
If I understand the patch 6/10 correctly, there is one (or a list) prog for
all lsm hooks per task which is not flexible enough.
For each LSM hook triggered by a thread, all of its Landlock eBPF
programs (dedicated for this kind of hook) will be evaluated (if
needed). This is the same behavior as seccomp (list of BPF programs
attached to a process hierarchy) except the BPF programs are not
evaluated for syscall but for LSM hooks. There is no way to make it more
fine-grained :)
There is a way to attach different bpf program per cgroup
and per lsm hook. Such approach drastically reduces overhead
of sandboxed application.
quoted
Anoop Naravaram's use case is to control the ports the applications
under cgroup can bind and listen on.
Such use case can be solved by such 'lsm cgroup controller' by
attaching bpf program to security_socket_bind lsm hook and
filtering sockaddr.
Furthermore Sargun's use case is to allow further sockaddr rewrites
from the bpf program which can be done as natural extension
of such mechanism.
If I understood Daniel's Anoop's Sargun's and yours use cases
correctly the common piece of kernel infrastructure that can solve
them all can start from Daniel's current set of patches that
establish a mechanism of attaching bpf program to a cgroup.
Then adding lsm hooks to it and later allowing argument rewrite
(since they're already in the kernel and no ToCToU problems exist)
To sum up, the pieces we have in common are the eBPF use and (optionally
for Landlock) there execution depending of the current cgroup.
Moreover, the three other series (Sargun's, Anoop's and Daniel's) do not
deal with unprivileged process which is the main purpose of Landlock.
I'm not sure that allowing sockaddr rewrites is a good idea here... Like
other LSMs, Landlock is dedicated to access-control.
we have to find common ground and common infra to solve all these use cases.
Pointing out the differences isn't going to make this snowflake
any more special.
For the network-related series, I think it make more sense to simply
create a netfilter rule matching a cgroup and then add more features to
netfilter (restrict port ranges and so on) thanks to eBPF programs.
Containers are (usually) in a dedicated network namespace, which open
the possibility to not only rely on cgroups (e.g. match UID,
netmask...). It would also be more flexible to be able to load a BPF
program in netfilter and update its maps on the fly to make dynamic
rules, like ipset does, but in a more generic way.
quoted
As far as safety and type checking that bpf programs has to do,
I like the approach of patch 06/10:
+LANDLOCK_HOOK2(file_open, FILE_OPEN,
+ PTR_TO_STRUCT_FILE, struct file *, file,
+ PTR_TO_STRUCT_CRED, const struct cred *, cred
+)
teaching verifier to recognize struct file, cred, sockaddr
will let bpf program access them naturally without any overhead.
Though:
is a bit of overkill.
I think it would be cleaner to have single
BPF_PROG_TYPE_LSM and at program load time pass
lsm_hook_id as well, so that verifier can do safety checks
based on type info provided in LANDLOCK_HOOKs
I first started with a unique BPF_PROG_TYPE but, the thing is, the BPF
verifier check programs according to their types. If we need to check
specific context value types (e.g. PTR_TO_STRUCT_FILE), we need a
dedicated program types. I don't see any other way to do it with the
current verifier code. Moreover it's the purpose of program types, right?
Adding new bpf program type for every lsm hook is not acceptable.
Either do one new program type + pass lsm_hook_id as suggested
or please come up with an alternative approach.
From: Andy Lutomirski <luto@amacapital.net> Date: 2016-08-27 07:31:01
On Aug 27, 2016 1:05 AM, "Alexei Starovoitov"
[off-list ref] wrote:
On Fri, Aug 26, 2016 at 05:10:40PM +0200, Mickaël Salaün wrote:
quoted
trimming cc list again. When it's too big vger will consider it as spam.
quoted
On 26/08/2016 04:14, Alexei Starovoitov wrote:
quoted
On Thu, Aug 25, 2016 at 12:32:44PM +0200, Mickaël Salaün wrote:
quoted
Add an eBPF function bpf_landlock_cmp_cgroup_beneath(opt, map, map_op)
to compare the current process cgroup with a cgroup handle, The handle
can match the current cgroup if it is the same or a child. This allows
to make conditional rules according to the current cgroup.
A cgroup handle is a map entry created from a file descriptor referring
a cgroup directory (e.g. by opening /sys/fs/cgroup/X). In this case, the
map entry is of type BPF_MAP_HANDLE_TYPE_LANDLOCK_CGROUP_FD and the
inferred array map is of type BPF_MAP_ARRAY_TYPE_LANDLOCK_CGROUP.
An unprivileged process can create and manipulate cgroups thanks to
cgroup delegation.
Signed-off-by: Mickaël Salaün <redacted>
...
quoted
+static inline u64 bpf_landlock_cmp_cgroup_beneath(u64 r1_option, u64 r2_map,
+ u64 r3_map_op, u64 r4, u64 r5)
+{
+ u8 option = (u8) r1_option;
+ struct bpf_map *map = (struct bpf_map *) (unsigned long) r2_map;
+ enum bpf_map_array_op map_op = r3_map_op;
+ struct bpf_array *array = container_of(map, struct bpf_array, map);
+ struct cgroup *cg1, *cg2;
+ struct map_landlock_handle *handle;
+ int i;
+
+ /* ARG_CONST_PTR_TO_LANDLOCK_HANDLE_CGROUP is an arraymap */
+ if (unlikely(!map)) {
+ WARN_ON(1);
+ return -EFAULT;
+ }
+ if (unlikely((option | _LANDLOCK_FLAG_OPT_MASK) != _LANDLOCK_FLAG_OPT_MASK))
+ return -EINVAL;
+
+ /* for now, only handle OP_OR */
+ switch (map_op) {
+ case BPF_MAP_ARRAY_OP_OR:
+ break;
+ case BPF_MAP_ARRAY_OP_UNSPEC:
+ case BPF_MAP_ARRAY_OP_AND:
+ case BPF_MAP_ARRAY_OP_XOR:
+ default:
+ return -EINVAL;
+ }
+
+ synchronize_rcu();
+
+ for (i = 0; i < array->n_entries; i++) {
+ handle = (struct map_landlock_handle *)
+ (array->value + array->elem_size * i);
+
+ /* protected by the proto types, should not happen */
+ if (unlikely(handle->type != BPF_MAP_HANDLE_TYPE_LANDLOCK_CGROUP_FD)) {
+ WARN_ON(1);
+ return -EFAULT;
+ }
+ if (unlikely(!handle->css)) {
+ WARN_ON(1);
+ return -EFAULT;
+ }
+
+ if (option & LANDLOCK_FLAG_OPT_REVERSE) {
+ cg1 = handle->css->cgroup;
+ cg2 = task_css_set(current)->dfl_cgrp;
+ } else {
+ cg1 = task_css_set(current)->dfl_cgrp;
+ cg2 = handle->css->cgroup;
+ }
+
+ if (cgroup_is_descendant(cg1, cg2))
+ return 0;
+ }
+ return 1;
+}
- please take a loook at exisiting bpf_current_task_under_cgroup and
reuse BPF_MAP_TYPE_CGROUP_ARRAY as a minimum. Doing new cgroup array
is nothing but duplication of the code.
Oh, I didn't know about this patchset and the new helper. Indeed, it
looks a lot like mine except there is no static verification of the map
type as I did with the arraymap of handles, and no batch mode either. I
think the return value of bpf_current_task_under_cgroup is error-prone
if an eBPF program do an "if(ret)" test on the value (because of the
negative ERRNO return value). Inverting the 0 and 1 return values should
fix this (0 == succeed, 1 == failed, <0 == error).
nothing to fix. It's good as-is. Use if (ret > 0) instead.
quoted
To sum up, there is four related patchsets:
* "Landlock LSM: Unprivileged sandboxing" (this series)
* "Add Checmate, BPF-driven minor LSM" (Sargun Dhillon)
* "Networking cgroup controller" (Anoop Naravaram)
* "Add eBPF hooks for cgroups" (Daniel Mack)
The three other series (Sargun's, Anoop's and Daniel's) are mainly
focused on network access-control via cgroup for *containers*. As far as
I can tell, only a *root* user (CAP_SYS_ADMIN) can use them. Landlock's
goal is to empower all processes (privileged or not) to create their own
sandbox. This also means, like explained in "[RFC v2 00/10] Landlock
LSM: Unprivileged sandboxing", there is more constraints. For example,
it is not acceptable to let a process probe the kernel memory as it
wish. More details are in the Landlock cover-letter.
Another important point is that supporting cgroup for Landlock is
optional. It does not rely on cgroup to be usable but is only a feature
available when (unprivileged) users can manage there own cgroup, which
is an important constraint. Put another way, Landlock should not rely on
cgroup to create sandboxes. Indeed, a process creating a sandbox do not
necessarily have access to the cgroup mount point (directly or not).
cgroup is the common way to group multiple tasks.
Without cgroup only parent<->child relationship will be possible,
which will limit usability of such lsm to a master task that controls
its children. Such api restriction would have been ok, if we could
extend it in the future, but unfortunately task-centric won't allow it
without creating a parallel lsm that is cgroup based.
Therefore I think we have to go with cgroup-centric api and your
application has to use cgroups from the start though only parent-child
would have been enough.
Also I don't think the kernel can afford two bpf based lsm. One task
based and another cgroup based, so we have to find common ground
that suits both use cases.
Having unprivliged access is a subset. There is no strong reason why
cgroup+lsm+bpf should be limited to root only always.
When we can guarantee no pointer leaks, we can allow unpriv.
I don't really understand what you mean. In the context of landlock,
which is a *sandbox*, can one of you explain a use case that
materially benefits from this type of cgroup usage? I haven't thought
of one.
From: Andy Lutomirski <luto@amacapital.net> Date: 2016-08-27 07:45:42
On Thu, Aug 25, 2016 at 3:32 AM, Mickaël Salaün [off-list ref] wrote:
Hi,
This series is a proof of concept to fill some missing part of seccomp as the
ability to check syscall argument pointers or creating more dynamic security
policies. The goal of this new stackable Linux Security Module (LSM) called
Landlock is to allow any process, including unprivileged ones, to create
powerful security sandboxes comparable to the Seatbelt/XNU Sandbox or the
OpenBSD Pledge. This kind of sandbox help to mitigate the security impact of
bugs or unexpected/malicious behaviors in userland applications.
The first RFC [1] was focused on extending seccomp while staying at the syscall
level. This brought a working PoC but with some (mitigated) ToCToU race
conditions due to the seccomp ptrace hole (now fixed) and the non-atomic
syscall argument evaluation (hence the LSM hooks).
# Landlock LSM
This second RFC is a fresh revamp of the code while keeping some working ideas.
This series is mainly focused on LSM hooks, while keeping the possibility to
tied them to syscalls. This new code removes all race conditions by design. It
now use eBPF instead of a subset of cBPF (as used by seccomp-bpf). This allow
to remove the previous stacked cBPF hack to do complex access checks thanks to
dedicated eBPF functions. An eBPF program is still very limited (i.e. can only
call a whitelist of functions) and can not do a denial of service (i.e. no
loop). The other major improvement is the replacement of the previous custom
checker groups of syscall arguments with a new dedicated eBPF map to collect
and compare Landlock handles with system resources (e.g. files or network
connections).
The approach taken is to add the minimum amount of code while still allowing
the userland to create quite complex access rules. A dedicated security policy
language such as used by SELinux, AppArmor and other major LSMs is a lot of
code and dedicated to a trusted process (i.e. root/administrator).
I think there might be a problem with the current design. If I add a
seccomp filter that uses RET_LANDLOCK and some landlock filters, what
happens if a second seccomp filter *also* uses RET_LANDLOCK? I think
they'll interfere with each other. It might end up being necessary to
require only one landlock seccomp layer at a time or to find a way to
stick all the filters in a layer together with the LSM callbacks or
maybe to just drop RET_LANDLOCK and let the callbacks look at the
syscall args.
BTW, what happens if an LSM hook is called outside a syscall context,
e.g. from a page fault?
# Sandbox example with conditional access control depending on cgroup
$ mkdir /sys/fs/cgroup/sandboxed
$ ls /home
user1
$ LANDLOCK_CGROUPS='/sys/fs/cgroup/sandboxed' \
LANDLOCK_ALLOWED='/bin:/lib:/usr:/tmp:/proc/self/fd/0' \
./sandbox /bin/sh -i
$ ls /home
user1
$ echo $$ > /sys/fs/cgroup/sandboxed/cgroup.procs
$ ls /home
ls: cannot open directory '/home': Permission denied
Something occurs to me that isn't strictly relevant to landlock but
may be relevant to unprivileged cgroups: can you cause trouble by
setting up a nastily-configured cgroup and running a setuid program in
it?
On Thu, Aug 25, 2016 at 7:10 AM, Mickaël Salaün [off-list ref] wrote:
quoted
On 25/08/2016 13:12, Andy Lutomirski wrote:
quoted
On Thu, Aug 25, 2016 at 3:32 AM, Mickaël Salaün [off-list ref] wrote:
quoted
Add eBPF functions to compare file system access with a Landlock file
system handle:
* bpf_landlock_cmp_fs_prop_with_struct_file(prop, map, map_op, file)
This function allows to compare the dentry, inode, device or mount
point of the currently accessed file, with a reference handle.
* bpf_landlock_cmp_fs_beneath_with_struct_file(opt, map, map_op, file)
This function allows an eBPF program to check if the current accessed
file is the same or in the hierarchy of a reference handle.
The goal of file system handle is to abstract kernel objects such as a
struct file or a struct inode. Userland can create this kind of handle
thanks to the BPF_MAP_UPDATE_ELEM command. The element is a struct
landlock_handle containing the handle type (e.g.
BPF_MAP_HANDLE_TYPE_LANDLOCK_FS_FD) and a file descriptor. This could
also be any descriptions able to match a struct file or a struct inode
(e.g. path or glob string).
This needs Eric's opinion.
Also, where do all the struct file *'s get stashed? Are they
preserved in the arraymap? What prevents reference cycles or absurdly
large numbers of struct files getting pinned?
Yes, the struct file are kept in the arraymap and dropped when there is
no more reference on them. Currently, the limitations are the maximum
number of open file descriptors referring to an arraymap and the maximum
number of eBPF Landlock programs loaded in a process
(LANDLOCK_PROG_LIST_MAX_PAGES in kernel/seccomp.c).
What kind of reference cycles have you in mind?
Shoving evil things into the arraymaps, e.g. unix sockets with
SCM_RIGHTS messages pending, eBPF program references, the arraymap fd
itself, another arraymap fd, etc.
The arraymap of Landlock handles is strongly typed and can check the
kind of FD it get when creating/updating an entry, which is done for the
cgroup type. It may be wise to add another check for FS types as well,
which should be a one-liner. I'll do it for the next round.
quoted
It probably needs another limit for kernel object references as well.
What is the best option here? Add another static limitation or use an
existing one?
Dunno. If RLIMIT_FILE could be made to work, that would be nice.
The RLIMIT_NOFILE is used for the eBPF map creation, but only the memory
limit is used to store the map entries (struct file pointers). I'll add
a new static limit for the number of FD-based arraymap entries because
it does not reflect the same semantic. The struct files are not usable
as FD, their only purpose is to be able to compare with another file.
Mickaël
On Fri, Aug 26, 2016 at 05:10:40PM +0200, Mickaël Salaün wrote:
quoted
quoted
- I don't think such 'for' loop can scale. The solution needs to work
with thousands of containers and thousands of cgroups.
In the patch 06/10 the proposal is to use 'current' as holder of
the programs:
+ for (prog = current->seccomp.landlock_prog;
+ prog; prog = prog->prev) {
+ if (prog->filter == landlock_ret->filter) {
+ cur_ret = BPF_PROG_RUN(prog->prog, (void *)&ctx);
+ break;
+ }
+ }
imo that's the root of scalability issue.
I think to be able to scale the bpf programs have to be attached to
cgroups instead of tasks.
That would be very different api. seccomp doesn't need to be touched.
But that is the only way I see to be able to scale.
Landlock is inspired from seccomp which also use a BPF program per
thread. For seccomp, each BPF programs are executed for each syscall.
For Landlock, some BPF programs are executed for some LSM hooks. I don't
see why it is a scale issue for Landlock comparing to seccomp. I also
don't see why storing the BPF program list pointer in the cgroup struct
instead of the task struct change a lot here. The BPF programs execution
will be the same anyway (for each LSM hook). Kees should probably have a
better opinion on this.
seccomp has its own issues and copying them doesn't make this lsm any better.
Like seccomp bpf programs are all gigantic switch statement that looks
for interesting syscall numbers. All syscalls of a task are paying
non-trivial seccomp penalty due to such design. If bpf was attached per
syscall it would have been much cheaper. Of course doing it this way
for seccomp is not easy, but for lsm such facility is already there.
Blank call of a single bpf prog for all lsm hooks is unnecessary
overhead that can and should be avoided.
It's probably a misunderstanding. Contrary to seccomp which run all the
thread's BPF programs for any syscall, Landlock only run eBPF programs
for the triggered LSM hooks, if their type match. Indeed, thanks to the
multiple eBPF program types and contrary to seccomp, Landlock only run
an eBPF program when needed. Landlock will have almost no performance
overhead if the syscalls do not trigger the watched LSM hooks for the
current process.
quoted
quoted
May be another way of thinking about it is 'lsm cgroup controller'
that Sargun is proposing.
The lsm hooks will provide stable execution points and the programs
will be called like:
prog = task_css_set(current)->dfl_cgrp->bpf.prog_effective[lsm_hook_id];
BPF_PROG_RUN(prog, ctx);
The delegation functionality and 'prog_effective' logic that
Daniel Mack is proposing will be fully reused here.
External container management software will be able to apply bpf
programs to control tasks under cgroup and such
bpf_landlock_cmp_cgroup_beneath() helper won't be necessary.
The user will be able to register different programs for different lsm hooks.
If I understand the patch 6/10 correctly, there is one (or a list) prog for
all lsm hooks per task which is not flexible enough.
For each LSM hook triggered by a thread, all of its Landlock eBPF
programs (dedicated for this kind of hook) will be evaluated (if
needed). This is the same behavior as seccomp (list of BPF programs
attached to a process hierarchy) except the BPF programs are not
evaluated for syscall but for LSM hooks. There is no way to make it more
fine-grained :)
There is a way to attach different bpf program per cgroup
and per lsm hook. Such approach drastically reduces overhead
of sandboxed application.
As said above, Landlock will not run an eBPF programs when not strictly
needed. Attaching to a cgroup will have the same performance impact as
attaching to a process hierarchy.
On Fri, Aug 26, 2016 at 05:10:40PM +0200, Mickaël Salaün wrote:
quoted
To sum up, there is four related patchsets:
* "Landlock LSM: Unprivileged sandboxing" (this series)
* "Add Checmate, BPF-driven minor LSM" (Sargun Dhillon)
* "Networking cgroup controller" (Anoop Naravaram)
* "Add eBPF hooks for cgroups" (Daniel Mack)
quoted
quoted
Anoop Naravaram's use case is to control the ports the applications
under cgroup can bind and listen on.
Such use case can be solved by such 'lsm cgroup controller' by
attaching bpf program to security_socket_bind lsm hook and
filtering sockaddr.
Furthermore Sargun's use case is to allow further sockaddr rewrites
from the bpf program which can be done as natural extension
of such mechanism.
If I understood Daniel's Anoop's Sargun's and yours use cases
correctly the common piece of kernel infrastructure that can solve
them all can start from Daniel's current set of patches that
establish a mechanism of attaching bpf program to a cgroup.
Then adding lsm hooks to it and later allowing argument rewrite
(since they're already in the kernel and no ToCToU problems exist)
quoted
For the network-related series, I think it make more sense to simply
create a netfilter rule matching a cgroup and then add more features to
netfilter (restrict port ranges and so on) thanks to eBPF programs.
Containers are (usually) in a dedicated network namespace, which open
the possibility to not only rely on cgroups (e.g. match UID,
netmask...). It would also be more flexible to be able to load a BPF
program in netfilter and update its maps on the fly to make dynamic
rules, like ipset does, but in a more generic way.
On Fri, Aug 26, 2016 at 05:10:40PM +0200, Mickaël Salaün wrote:
quoted
quoted
As far as safety and type checking that bpf programs has to do,
I like the approach of patch 06/10:
+LANDLOCK_HOOK2(file_open, FILE_OPEN,
+ PTR_TO_STRUCT_FILE, struct file *, file,
+ PTR_TO_STRUCT_CRED, const struct cred *, cred
+)
teaching verifier to recognize struct file, cred, sockaddr
will let bpf program access them naturally without any overhead.
Though:
is a bit of overkill.
I think it would be cleaner to have single
BPF_PROG_TYPE_LSM and at program load time pass
lsm_hook_id as well, so that verifier can do safety checks
based on type info provided in LANDLOCK_HOOKs
I first started with a unique BPF_PROG_TYPE but, the thing is, the BPF
verifier check programs according to their types. If we need to check
specific context value types (e.g. PTR_TO_STRUCT_FILE), we need a
dedicated program types. I don't see any other way to do it with the
current verifier code. Moreover it's the purpose of program types, right?
Adding new bpf program type for every lsm hook is not acceptable.
Either do one new program type + pass lsm_hook_id as suggested
or please come up with an alternative approach.
OK, so we have to modify the verifier to not only rely on the program
type but on another value to check the context accesses. Do you have a
hint from where this value could come from? Do we need to add a new bpf
command to associate a program to a subtype?
On Thu, Aug 25, 2016 at 3:32 AM, Mickaël Salaün [off-list ref] wrote:
quoted
Hi,
This series is a proof of concept to fill some missing part of seccomp as the
ability to check syscall argument pointers or creating more dynamic security
policies. The goal of this new stackable Linux Security Module (LSM) called
Landlock is to allow any process, including unprivileged ones, to create
powerful security sandboxes comparable to the Seatbelt/XNU Sandbox or the
OpenBSD Pledge. This kind of sandbox help to mitigate the security impact of
bugs or unexpected/malicious behaviors in userland applications.
The first RFC [1] was focused on extending seccomp while staying at the syscall
level. This brought a working PoC but with some (mitigated) ToCToU race
conditions due to the seccomp ptrace hole (now fixed) and the non-atomic
syscall argument evaluation (hence the LSM hooks).
# Landlock LSM
This second RFC is a fresh revamp of the code while keeping some working ideas.
This series is mainly focused on LSM hooks, while keeping the possibility to
tied them to syscalls. This new code removes all race conditions by design. It
now use eBPF instead of a subset of cBPF (as used by seccomp-bpf). This allow
to remove the previous stacked cBPF hack to do complex access checks thanks to
dedicated eBPF functions. An eBPF program is still very limited (i.e. can only
call a whitelist of functions) and can not do a denial of service (i.e. no
loop). The other major improvement is the replacement of the previous custom
checker groups of syscall arguments with a new dedicated eBPF map to collect
and compare Landlock handles with system resources (e.g. files or network
connections).
The approach taken is to add the minimum amount of code while still allowing
the userland to create quite complex access rules. A dedicated security policy
language such as used by SELinux, AppArmor and other major LSMs is a lot of
code and dedicated to a trusted process (i.e. root/administrator).
I think there might be a problem with the current design. If I add a
seccomp filter that uses RET_LANDLOCK and some landlock filters, what
happens if a second seccomp filter *also* uses RET_LANDLOCK? I think
they'll interfere with each other. It might end up being necessary to
require only one landlock seccomp layer at a time or to find a way to
stick all the filters in a layer together with the LSM callbacks or
maybe to just drop RET_LANDLOCK and let the callbacks look at the
syscall args.
This is correctly managed. For each RET_LANDLOCK, if there is one or
more associated Landlock programs (i.e. created by the same thread after
this seccomp filters), there is one Landlock program instance run for
each seccomp that trigger them. This way, each cookie linked to a
RET_LANDLOCK is evaluated one time by each relevant Landlock program.
Example when a thread that loaded multiple seccomp filters (SF) and
multiple Landlock programs (LP) associated with one LSM hook: SF0, SF1,
LP0(file_open), SF2, LP1(file_open), LP2(file_permission)
* If SF0 returns RET_LANDLOCK(cookie0), then LP0 and LP1 are run with
cookie0 if the current syscall trigger the file_open hook, and LP2 is
run with cookie0 if the syscall trigger the file_permission hook.
* In addition to the previous case, if SF1 returns
RET_LANDLOCK(cookie1), then LP0 and LP1 are run with cookie1 if the
current syscall trigger the file_open hook, and LP2 is run with cookie1
if the syscall trigger the file_permission hook.
* In addition to the previous cases, if SF2 returns
RET_LANDLOCK(cookie2), then (only) LP1 is run with cookie2 if the
current syscall trigger the file_open hook, and LP2 is run with cookie2
if the syscall trigger the file_permission hook.
BTW, what happens if an LSM hook is called outside a syscall context,
e.g. from a page fault?
Good catch! For now, only a syscall can trigger an LSM hook because of
the RET_LANDLOCK constraint. It may be wise to trigger them without a
cookie and add a dedicated variable in the eBPF context.
quoted
# Sandbox example with conditional access control depending on cgroup
$ mkdir /sys/fs/cgroup/sandboxed
$ ls /home
user1
$ LANDLOCK_CGROUPS='/sys/fs/cgroup/sandboxed' \
LANDLOCK_ALLOWED='/bin:/lib:/usr:/tmp:/proc/self/fd/0' \
./sandbox /bin/sh -i
$ ls /home
user1
$ echo $$ > /sys/fs/cgroup/sandboxed/cgroup.procs
$ ls /home
ls: cannot open directory '/home': Permission denied
Something occurs to me that isn't strictly relevant to landlock but
may be relevant to unprivileged cgroups: can you cause trouble by
setting up a nastily-configured cgroup and running a setuid program in
it?
I hope not… But the use of cgroups should not be mandatory for Landlock.
Cc Tejun and the cgroups ML.
On 27/08/2016 17:10, Mickaël Salaün wrote:
On 27/08/2016 09:40, Andy Lutomirski wrote:
quoted
On Thu, Aug 25, 2016 at 3:32 AM, Mickaël Salaün [off-list ref] wrote:
quoted
# Sandbox example with conditional access control depending on cgroup
$ mkdir /sys/fs/cgroup/sandboxed
$ ls /home
user1
$ LANDLOCK_CGROUPS='/sys/fs/cgroup/sandboxed' \
LANDLOCK_ALLOWED='/bin:/lib:/usr:/tmp:/proc/self/fd/0' \
./sandbox /bin/sh -i
$ ls /home
user1
$ echo $$ > /sys/fs/cgroup/sandboxed/cgroup.procs
$ ls /home
ls: cannot open directory '/home': Permission denied
Something occurs to me that isn't strictly relevant to landlock but
may be relevant to unprivileged cgroups: can you cause trouble by
setting up a nastily-configured cgroup and running a setuid program in
it?
I hope not… But the use of cgroups should not be mandatory for Landlock.
In a previous email:
On 26/08/2016 17:50, Tejun Heo wrote:
I haven't looked in detail but in general I'm not too excited about
layering security mechanism on top of cgroup. Maybe it makes some
sense when security domain coincides with resource domains but at any
rate please keep me in the loop.
On Sat, Aug 27, 2016 at 04:06:38PM +0200, Mickaël Salaün wrote:
On 27/08/2016 01:05, Alexei Starovoitov wrote:
quoted
On Fri, Aug 26, 2016 at 05:10:40PM +0200, Mickaël Salaün wrote:
quoted
quoted
- I don't think such 'for' loop can scale. The solution needs to work
with thousands of containers and thousands of cgroups.
In the patch 06/10 the proposal is to use 'current' as holder of
the programs:
+ for (prog = current->seccomp.landlock_prog;
+ prog; prog = prog->prev) {
+ if (prog->filter == landlock_ret->filter) {
+ cur_ret = BPF_PROG_RUN(prog->prog, (void *)&ctx);
+ break;
+ }
+ }
imo that's the root of scalability issue.
I think to be able to scale the bpf programs have to be attached to
cgroups instead of tasks.
That would be very different api. seccomp doesn't need to be touched.
But that is the only way I see to be able to scale.
Landlock is inspired from seccomp which also use a BPF program per
thread. For seccomp, each BPF programs are executed for each syscall.
For Landlock, some BPF programs are executed for some LSM hooks. I don't
see why it is a scale issue for Landlock comparing to seccomp. I also
don't see why storing the BPF program list pointer in the cgroup struct
instead of the task struct change a lot here. The BPF programs execution
will be the same anyway (for each LSM hook). Kees should probably have a
better opinion on this.
seccomp has its own issues and copying them doesn't make this lsm any better.
Like seccomp bpf programs are all gigantic switch statement that looks
for interesting syscall numbers. All syscalls of a task are paying
non-trivial seccomp penalty due to such design. If bpf was attached per
syscall it would have been much cheaper. Of course doing it this way
for seccomp is not easy, but for lsm such facility is already there.
Blank call of a single bpf prog for all lsm hooks is unnecessary
overhead that can and should be avoided.
It's probably a misunderstanding. Contrary to seccomp which run all the
thread's BPF programs for any syscall, Landlock only run eBPF programs
for the triggered LSM hooks, if their type match. Indeed, thanks to the
multiple eBPF program types and contrary to seccomp, Landlock only run
an eBPF program when needed. Landlock will have almost no performance
overhead if the syscalls do not trigger the watched LSM hooks for the
current process.
that's not what I see in the patch 06/10:
all lsm_hooks in 'static struct security_hook_list landlock_hooks'
(which eventually means all lsm hooks) will call
static inline int landlock_hook_##NAME
which will call landlock_run_prog()
which does:
+ for (landlock_ret = current->seccomp.landlock_ret;
+ landlock_ret; landlock_ret = landlock_ret->prev) {
+ if (landlock_ret->triggered) {
+ ctx.cookie = landlock_ret->cookie;
+ for (prog = current->seccomp.landlock_prog;
+ prog; prog = prog->prev) {
+ if (prog->filter == landlock_ret->filter) {
+ cur_ret = BPF_PROG_RUN(prog->prog, (void *)&ctx);
+ break;
+ }
+ }
that is unacceptable overhead and not a scalable design.
It kinda works for 3 lsm_hooks as in patch 6, but doesn't scale
as soon as more lsm hooks are added.
As said above, Landlock will not run an eBPF programs when not strictly
needed. Attaching to a cgroup will have the same performance impact as
attaching to a process hierarchy.
Having a prog per cgroup per lsm_hook is the only scalable way I
could come up with. If you see another way, please propose.
current->seccomp.landlock_prog is not the answer.
On Sat, Aug 27, 2016 at 12:30:36AM -0700, Andy Lutomirski wrote:
quoted
cgroup is the common way to group multiple tasks.
Without cgroup only parent<->child relationship will be possible,
which will limit usability of such lsm to a master task that controls
its children. Such api restriction would have been ok, if we could
extend it in the future, but unfortunately task-centric won't allow it
without creating a parallel lsm that is cgroup based.
Therefore I think we have to go with cgroup-centric api and your
application has to use cgroups from the start though only parent-child
would have been enough.
Also I don't think the kernel can afford two bpf based lsm. One task
based and another cgroup based, so we have to find common ground
that suits both use cases.
Having unprivliged access is a subset. There is no strong reason why
cgroup+lsm+bpf should be limited to root only always.
When we can guarantee no pointer leaks, we can allow unpriv.
I don't really understand what you mean. In the context of landlock,
which is a *sandbox*, can one of you explain a use case that
materially benefits from this type of cgroup usage? I haven't thought
of one.
In case of seccomp-like sandbox where parent controls child processes
cgroup is not needed. It's needed when container management software
needs to control a set of applications. If we can have one bpf-based lsm
that works via cgroup and without, I'd be fine with it. Right now
I haven't seen a plausible proposal to do that. Therefore cgroup based
api is a common api that works for sandbox as well, though requiring
parent to create a cgroup just to control a single child is cumbersome.
On Sat, Aug 27, 2016 at 04:34:55PM +0200, Mickaël Salaün wrote:
On 27/08/2016 01:05, Alexei Starovoitov wrote:
quoted
On Fri, Aug 26, 2016 at 05:10:40PM +0200, Mickaël Salaün wrote:
quoted
quoted
As far as safety and type checking that bpf programs has to do,
I like the approach of patch 06/10:
+LANDLOCK_HOOK2(file_open, FILE_OPEN,
+ PTR_TO_STRUCT_FILE, struct file *, file,
+ PTR_TO_STRUCT_CRED, const struct cred *, cred
+)
teaching verifier to recognize struct file, cred, sockaddr
will let bpf program access them naturally without any overhead.
Though:
is a bit of overkill.
I think it would be cleaner to have single
BPF_PROG_TYPE_LSM and at program load time pass
lsm_hook_id as well, so that verifier can do safety checks
based on type info provided in LANDLOCK_HOOKs
I first started with a unique BPF_PROG_TYPE but, the thing is, the BPF
verifier check programs according to their types. If we need to check
specific context value types (e.g. PTR_TO_STRUCT_FILE), we need a
dedicated program types. I don't see any other way to do it with the
current verifier code. Moreover it's the purpose of program types, right?
Adding new bpf program type for every lsm hook is not acceptable.
Either do one new program type + pass lsm_hook_id as suggested
or please come up with an alternative approach.
OK, so we have to modify the verifier to not only rely on the program
type but on another value to check the context accesses. Do you have a
hint from where this value could come from? Do we need to add a new bpf
command to associate a program to a subtype?
It's another field prog_subtype (or prog_hook_id) in union bpf_attr.
Both prog_type and prog_hook_id are used during verification.
prog_type distinguishes the main aspects whereas prog_hook_id selects
which lsm_hook's argument definition to apply.
At the time of attaching to a hook, the prog_hook_id passed at the
load time should match lsm's hook_id.
On Sat, Aug 27, 2016 at 04:19:05PM +0200, Mickaël Salaün wrote:
On 27/08/2016 01:05, Alexei Starovoitov wrote:
quoted
On Fri, Aug 26, 2016 at 05:10:40PM +0200, Mickaël Salaün wrote:
quoted
To sum up, there is four related patchsets:
* "Landlock LSM: Unprivileged sandboxing" (this series)
* "Add Checmate, BPF-driven minor LSM" (Sargun Dhillon)
* "Networking cgroup controller" (Anoop Naravaram)
* "Add eBPF hooks for cgroups" (Daniel Mack)
quoted
quoted
quoted
Anoop Naravaram's use case is to control the ports the applications
under cgroup can bind and listen on.
Such use case can be solved by such 'lsm cgroup controller' by
attaching bpf program to security_socket_bind lsm hook and
filtering sockaddr.
Furthermore Sargun's use case is to allow further sockaddr rewrites
from the bpf program which can be done as natural extension
of such mechanism.
If I understood Daniel's Anoop's Sargun's and yours use cases
correctly the common piece of kernel infrastructure that can solve
them all can start from Daniel's current set of patches that
establish a mechanism of attaching bpf program to a cgroup.
Then adding lsm hooks to it and later allowing argument rewrite
(since they're already in the kernel and no ToCToU problems exist)
quoted
quoted
For the network-related series, I think it make more sense to simply
create a netfilter rule matching a cgroup and then add more features to
netfilter (restrict port ranges and so on) thanks to eBPF programs.
Containers are (usually) in a dedicated network namespace, which open
the possibility to not only rely on cgroups (e.g. match UID,
netmask...). It would also be more flexible to be able to load a BPF
program in netfilter and update its maps on the fly to make dynamic
rules, like ipset does, but in a more generic way.
What do the netdev folks think about this design?
such design doesn't scale when used for container management and
that's what we need to solve.
netns has its overhead and management issues. There are proposals to
solve that but that is orthogonal to containers in general.
A lot of them don't use netns.
On Sat, Aug 27, 2016 at 04:06:38PM +0200, Mickaël Salaün wrote:
quoted
On 27/08/2016 01:05, Alexei Starovoitov wrote:
quoted
On Fri, Aug 26, 2016 at 05:10:40PM +0200, Mickaël Salaün wrote:
quoted
quoted
- I don't think such 'for' loop can scale. The solution needs to work
with thousands of containers and thousands of cgroups.
In the patch 06/10 the proposal is to use 'current' as holder of
the programs:
+ for (prog = current->seccomp.landlock_prog;
+ prog; prog = prog->prev) {
+ if (prog->filter == landlock_ret->filter) {
+ cur_ret = BPF_PROG_RUN(prog->prog, (void *)&ctx);
+ break;
+ }
+ }
imo that's the root of scalability issue.
I think to be able to scale the bpf programs have to be attached to
cgroups instead of tasks.
That would be very different api. seccomp doesn't need to be touched.
But that is the only way I see to be able to scale.
Landlock is inspired from seccomp which also use a BPF program per
thread. For seccomp, each BPF programs are executed for each syscall.
For Landlock, some BPF programs are executed for some LSM hooks. I don't
see why it is a scale issue for Landlock comparing to seccomp. I also
don't see why storing the BPF program list pointer in the cgroup struct
instead of the task struct change a lot here. The BPF programs execution
will be the same anyway (for each LSM hook). Kees should probably have a
better opinion on this.
seccomp has its own issues and copying them doesn't make this lsm any better.
Like seccomp bpf programs are all gigantic switch statement that looks
for interesting syscall numbers. All syscalls of a task are paying
non-trivial seccomp penalty due to such design. If bpf was attached per
syscall it would have been much cheaper. Of course doing it this way
for seccomp is not easy, but for lsm such facility is already there.
Blank call of a single bpf prog for all lsm hooks is unnecessary
overhead that can and should be avoided.
It's probably a misunderstanding. Contrary to seccomp which run all the
thread's BPF programs for any syscall, Landlock only run eBPF programs
for the triggered LSM hooks, if their type match. Indeed, thanks to the
multiple eBPF program types and contrary to seccomp, Landlock only run
an eBPF program when needed. Landlock will have almost no performance
overhead if the syscalls do not trigger the watched LSM hooks for the
current process.
that's not what I see in the patch 06/10:
all lsm_hooks in 'static struct security_hook_list landlock_hooks'
(which eventually means all lsm hooks) will call
static inline int landlock_hook_##NAME
which will call landlock_run_prog()
which does:
+ for (landlock_ret = current->seccomp.landlock_ret;
+ landlock_ret; landlock_ret = landlock_ret->prev) {
+ if (landlock_ret->triggered) {
+ ctx.cookie = landlock_ret->cookie;
+ for (prog = current->seccomp.landlock_prog;
+ prog; prog = prog->prev) {
+ if (prog->filter == landlock_ret->filter) {
+ cur_ret = BPF_PROG_RUN(prog->prog, (void *)&ctx);
+ break;
+ }
+ }
that is unacceptable overhead and not a scalable design.
It kinda works for 3 lsm_hooks as in patch 6, but doesn't scale
as soon as more lsm hooks are added.
Good catch! I forgot to check the program (sub)type in the loop to only
run the needed programs for the current hook. I will fix this.
quoted
As said above, Landlock will not run an eBPF programs when not strictly
needed. Attaching to a cgroup will have the same performance impact as
attaching to a process hierarchy.
Having a prog per cgroup per lsm_hook is the only scalable way I
could come up with. If you see another way, please propose.
current->seccomp.landlock_prog is not the answer.
Hum, I don't see the difference from a performance point of view between
a cgroup-based or a process hierarchy-based system.
Maybe a better option should be to use an array of pointers with N
entries, one for each supported hook, instead of a unique pointer list?
Anyway, being able to attach an LSM hook program to a cgroup thanks to
the new BPF_PROG_ATTACH seems a good idea (while keeping the possibility
to use a process hierarchy). The downside will be to handle an LSM hook
program which is not triggered by a seccomp-filter, but this should be
needed anyway to handle interruptions.
On Sat, Aug 27, 2016 at 04:34:55PM +0200, Mickaël Salaün wrote:
quoted
On 27/08/2016 01:05, Alexei Starovoitov wrote:
quoted
On Fri, Aug 26, 2016 at 05:10:40PM +0200, Mickaël Salaün wrote:
quoted
quoted
As far as safety and type checking that bpf programs has to do,
I like the approach of patch 06/10:
+LANDLOCK_HOOK2(file_open, FILE_OPEN,
+ PTR_TO_STRUCT_FILE, struct file *, file,
+ PTR_TO_STRUCT_CRED, const struct cred *, cred
+)
teaching verifier to recognize struct file, cred, sockaddr
will let bpf program access them naturally without any overhead.
Though:
is a bit of overkill.
I think it would be cleaner to have single
BPF_PROG_TYPE_LSM and at program load time pass
lsm_hook_id as well, so that verifier can do safety checks
based on type info provided in LANDLOCK_HOOKs
I first started with a unique BPF_PROG_TYPE but, the thing is, the BPF
verifier check programs according to their types. If we need to check
specific context value types (e.g. PTR_TO_STRUCT_FILE), we need a
dedicated program types. I don't see any other way to do it with the
current verifier code. Moreover it's the purpose of program types, right?
Adding new bpf program type for every lsm hook is not acceptable.
Either do one new program type + pass lsm_hook_id as suggested
or please come up with an alternative approach.
OK, so we have to modify the verifier to not only rely on the program
type but on another value to check the context accesses. Do you have a
hint from where this value could come from? Do we need to add a new bpf
command to associate a program to a subtype?
It's another field prog_subtype (or prog_hook_id) in union bpf_attr.
Both prog_type and prog_hook_id are used during verification.
prog_type distinguishes the main aspects whereas prog_hook_id selects
which lsm_hook's argument definition to apply.
At the time of attaching to a hook, the prog_hook_id passed at the
load time should match lsm's hook_id.
OK, so this new prog_subtype field should be use/set by a new bpf_cmd,
right? Something like BPF_PROG_SUBTYPE or BPF_PROG_METADATA?
On Sat, Aug 27, 2016 at 09:35:14PM +0200, Mickaël Salaün wrote:
On 27/08/2016 20:06, Alexei Starovoitov wrote:
quoted
On Sat, Aug 27, 2016 at 04:06:38PM +0200, Mickaël Salaün wrote:
quoted
On 27/08/2016 01:05, Alexei Starovoitov wrote:
quoted
On Fri, Aug 26, 2016 at 05:10:40PM +0200, Mickaël Salaün wrote:
quoted
quoted
- I don't think such 'for' loop can scale. The solution needs to work
with thousands of containers and thousands of cgroups.
In the patch 06/10 the proposal is to use 'current' as holder of
the programs:
+ for (prog = current->seccomp.landlock_prog;
+ prog; prog = prog->prev) {
+ if (prog->filter == landlock_ret->filter) {
+ cur_ret = BPF_PROG_RUN(prog->prog, (void *)&ctx);
+ break;
+ }
+ }
imo that's the root of scalability issue.
I think to be able to scale the bpf programs have to be attached to
cgroups instead of tasks.
That would be very different api. seccomp doesn't need to be touched.
But that is the only way I see to be able to scale.
Landlock is inspired from seccomp which also use a BPF program per
thread. For seccomp, each BPF programs are executed for each syscall.
For Landlock, some BPF programs are executed for some LSM hooks. I don't
see why it is a scale issue for Landlock comparing to seccomp. I also
don't see why storing the BPF program list pointer in the cgroup struct
instead of the task struct change a lot here. The BPF programs execution
will be the same anyway (for each LSM hook). Kees should probably have a
better opinion on this.
seccomp has its own issues and copying them doesn't make this lsm any better.
Like seccomp bpf programs are all gigantic switch statement that looks
for interesting syscall numbers. All syscalls of a task are paying
non-trivial seccomp penalty due to such design. If bpf was attached per
syscall it would have been much cheaper. Of course doing it this way
for seccomp is not easy, but for lsm such facility is already there.
Blank call of a single bpf prog for all lsm hooks is unnecessary
overhead that can and should be avoided.
It's probably a misunderstanding. Contrary to seccomp which run all the
thread's BPF programs for any syscall, Landlock only run eBPF programs
for the triggered LSM hooks, if their type match. Indeed, thanks to the
multiple eBPF program types and contrary to seccomp, Landlock only run
an eBPF program when needed. Landlock will have almost no performance
overhead if the syscalls do not trigger the watched LSM hooks for the
current process.
that's not what I see in the patch 06/10:
all lsm_hooks in 'static struct security_hook_list landlock_hooks'
(which eventually means all lsm hooks) will call
static inline int landlock_hook_##NAME
which will call landlock_run_prog()
which does:
+ for (landlock_ret = current->seccomp.landlock_ret;
+ landlock_ret; landlock_ret = landlock_ret->prev) {
+ if (landlock_ret->triggered) {
+ ctx.cookie = landlock_ret->cookie;
+ for (prog = current->seccomp.landlock_prog;
+ prog; prog = prog->prev) {
+ if (prog->filter == landlock_ret->filter) {
+ cur_ret = BPF_PROG_RUN(prog->prog, (void *)&ctx);
+ break;
+ }
+ }
that is unacceptable overhead and not a scalable design.
It kinda works for 3 lsm_hooks as in patch 6, but doesn't scale
as soon as more lsm hooks are added.
Good catch! I forgot to check the program (sub)type in the loop to only
run the needed programs for the current hook. I will fix this.
quoted
quoted
As said above, Landlock will not run an eBPF programs when not strictly
needed. Attaching to a cgroup will have the same performance impact as
attaching to a process hierarchy.
Having a prog per cgroup per lsm_hook is the only scalable way I
could come up with. If you see another way, please propose.
current->seccomp.landlock_prog is not the answer.
Hum, I don't see the difference from a performance point of view between
a cgroup-based or a process hierarchy-based system.
Maybe a better option should be to use an array of pointers with N
entries, one for each supported hook, instead of a unique pointer list?
yes, clearly array dereference is faster than link list walk.
Now the question is where to keep this prog_array[num_lsm_hooks] ?
Since we cannot keep it inside task_struct, we have to allocate it.
Every time the task is creted then. What to do on the fork? That
will require changes all over. Then the obvious optimization would be
to share this allocated array of prog pointers across multiple tasks...
and little by little this new facility will look like cgroup.
Hence the suggestion to put this array into cgroup from the start.
Anyway, being able to attach an LSM hook program to a cgroup thanks to
the new BPF_PROG_ATTACH seems a good idea (while keeping the possibility
to use a process hierarchy). The downside will be to handle an LSM hook
program which is not triggered by a seccomp-filter, but this should be
needed anyway to handle interruptions.
what do you mean 'not triggered by seccomp' ?
You're not suggesting that this lsm has to enable seccomp to be functional?
imo that's non starter due to overhead.
On Sat, Aug 27, 2016 at 09:55:01PM +0200, Mickaël Salaün wrote:
On 27/08/2016 20:19, Alexei Starovoitov wrote:
quoted
On Sat, Aug 27, 2016 at 04:34:55PM +0200, Mickaël Salaün wrote:
quoted
On 27/08/2016 01:05, Alexei Starovoitov wrote:
quoted
On Fri, Aug 26, 2016 at 05:10:40PM +0200, Mickaël Salaün wrote:
quoted
quoted
As far as safety and type checking that bpf programs has to do,
I like the approach of patch 06/10:
+LANDLOCK_HOOK2(file_open, FILE_OPEN,
+ PTR_TO_STRUCT_FILE, struct file *, file,
+ PTR_TO_STRUCT_CRED, const struct cred *, cred
+)
teaching verifier to recognize struct file, cred, sockaddr
will let bpf program access them naturally without any overhead.
Though:
is a bit of overkill.
I think it would be cleaner to have single
BPF_PROG_TYPE_LSM and at program load time pass
lsm_hook_id as well, so that verifier can do safety checks
based on type info provided in LANDLOCK_HOOKs
I first started with a unique BPF_PROG_TYPE but, the thing is, the BPF
verifier check programs according to their types. If we need to check
specific context value types (e.g. PTR_TO_STRUCT_FILE), we need a
dedicated program types. I don't see any other way to do it with the
current verifier code. Moreover it's the purpose of program types, right?
Adding new bpf program type for every lsm hook is not acceptable.
Either do one new program type + pass lsm_hook_id as suggested
or please come up with an alternative approach.
OK, so we have to modify the verifier to not only rely on the program
type but on another value to check the context accesses. Do you have a
hint from where this value could come from? Do we need to add a new bpf
command to associate a program to a subtype?
It's another field prog_subtype (or prog_hook_id) in union bpf_attr.
Both prog_type and prog_hook_id are used during verification.
prog_type distinguishes the main aspects whereas prog_hook_id selects
which lsm_hook's argument definition to apply.
At the time of attaching to a hook, the prog_hook_id passed at the
load time should match lsm's hook_id.
OK, so this new prog_subtype field should be use/set by a new bpf_cmd,
right? Something like BPF_PROG_SUBTYPE or BPF_PROG_METADATA?
No new command. It will be an optional field to existing BPF_PROG_LOAD.
In other words instead of replicating everything that different
bpf_prog_type-s need, we can pass this hook_id field to fine tune
the purpose (and applicability) of the program.
And one BPF_PROG_TYPE_LANDLOCK type will cover all lsm hooks.
For example existing BPF_PROG_TYPE_TRACEPOINT checks the safety
for all tracepoints while they all have different arguments.
For tracepoints it's easier, since the only difference between them
is the range of ctx access. Here we need strong type safety
of arguments therefore need extra hook_id at load time.
On Sat, Aug 27, 2016 at 09:35:14PM +0200, Mickaël Salaün wrote:
quoted
On 27/08/2016 20:06, Alexei Starovoitov wrote:
quoted
On Sat, Aug 27, 2016 at 04:06:38PM +0200, Mickaël Salaün wrote:
quoted
As said above, Landlock will not run an eBPF programs when not strictly
needed. Attaching to a cgroup will have the same performance impact as
attaching to a process hierarchy.
Having a prog per cgroup per lsm_hook is the only scalable way I
could come up with. If you see another way, please propose.
current->seccomp.landlock_prog is not the answer.
Hum, I don't see the difference from a performance point of view between
a cgroup-based or a process hierarchy-based system.
Maybe a better option should be to use an array of pointers with N
entries, one for each supported hook, instead of a unique pointer list?
yes, clearly array dereference is faster than link list walk.
Now the question is where to keep this prog_array[num_lsm_hooks] ?
Since we cannot keep it inside task_struct, we have to allocate it.
Every time the task is creted then. What to do on the fork? That
will require changes all over. Then the obvious optimization would be
to share this allocated array of prog pointers across multiple tasks...
and little by little this new facility will look like cgroup.
Hence the suggestion to put this array into cgroup from the start.
I see your point :)
quoted
Anyway, being able to attach an LSM hook program to a cgroup thanks to
the new BPF_PROG_ATTACH seems a good idea (while keeping the possibility
to use a process hierarchy). The downside will be to handle an LSM hook
program which is not triggered by a seccomp-filter, but this should be
needed anyway to handle interruptions.
what do you mean 'not triggered by seccomp' ?
You're not suggesting that this lsm has to enable seccomp to be functional?
imo that's non starter due to overhead.
Yes, for now, it is triggered by a new seccomp filter return value
RET_LANDLOCK, which can take a 16-bit value called cookie. This must not
be needed but could be useful to bind a seccomp filter security policy
with a Landlock one. Waiting for Kees's point of view…
On Sat, Aug 27, 2016 at 09:55:01PM +0200, Mickaël Salaün wrote:
quoted
On 27/08/2016 20:19, Alexei Starovoitov wrote:
quoted
On Sat, Aug 27, 2016 at 04:34:55PM +0200, Mickaël Salaün wrote:
quoted
On 27/08/2016 01:05, Alexei Starovoitov wrote:
quoted
On Fri, Aug 26, 2016 at 05:10:40PM +0200, Mickaël Salaün wrote:
quoted
quoted
As far as safety and type checking that bpf programs has to do,
I like the approach of patch 06/10:
+LANDLOCK_HOOK2(file_open, FILE_OPEN,
+ PTR_TO_STRUCT_FILE, struct file *, file,
+ PTR_TO_STRUCT_CRED, const struct cred *, cred
+)
teaching verifier to recognize struct file, cred, sockaddr
will let bpf program access them naturally without any overhead.
Though:
is a bit of overkill.
I think it would be cleaner to have single
BPF_PROG_TYPE_LSM and at program load time pass
lsm_hook_id as well, so that verifier can do safety checks
based on type info provided in LANDLOCK_HOOKs
I first started with a unique BPF_PROG_TYPE but, the thing is, the BPF
verifier check programs according to their types. If we need to check
specific context value types (e.g. PTR_TO_STRUCT_FILE), we need a
dedicated program types. I don't see any other way to do it with the
current verifier code. Moreover it's the purpose of program types, right?
Adding new bpf program type for every lsm hook is not acceptable.
Either do one new program type + pass lsm_hook_id as suggested
or please come up with an alternative approach.
OK, so we have to modify the verifier to not only rely on the program
type but on another value to check the context accesses. Do you have a
hint from where this value could come from? Do we need to add a new bpf
command to associate a program to a subtype?
It's another field prog_subtype (or prog_hook_id) in union bpf_attr.
Both prog_type and prog_hook_id are used during verification.
prog_type distinguishes the main aspects whereas prog_hook_id selects
which lsm_hook's argument definition to apply.
At the time of attaching to a hook, the prog_hook_id passed at the
load time should match lsm's hook_id.
OK, so this new prog_subtype field should be use/set by a new bpf_cmd,
right? Something like BPF_PROG_SUBTYPE or BPF_PROG_METADATA?
No new command. It will be an optional field to existing BPF_PROG_LOAD.
In other words instead of replicating everything that different
bpf_prog_type-s need, we can pass this hook_id field to fine tune
the purpose (and applicability) of the program.
And one BPF_PROG_TYPE_LANDLOCK type will cover all lsm hooks.
For example existing BPF_PROG_TYPE_TRACEPOINT checks the safety
for all tracepoints while they all have different arguments.
For tracepoints it's easier, since the only difference between them
is the range of ctx access. Here we need strong type safety
of arguments therefore need extra hook_id at load time.
From: Andy Lutomirski <luto@amacapital.net> Date: 2016-08-28 08:13:39
On Aug 27, 2016 11:14 PM, "Mickaël Salaün" [off-list ref] wrote:
On 27/08/2016 22:43, Alexei Starovoitov wrote:
quoted
On Sat, Aug 27, 2016 at 09:35:14PM +0200, Mickaël Salaün wrote:
quoted
On 27/08/2016 20:06, Alexei Starovoitov wrote:
quoted
On Sat, Aug 27, 2016 at 04:06:38PM +0200, Mickaël Salaün wrote:
quoted
As said above, Landlock will not run an eBPF programs when not strictly
needed. Attaching to a cgroup will have the same performance impact as
attaching to a process hierarchy.
Having a prog per cgroup per lsm_hook is the only scalable way I
could come up with. If you see another way, please propose.
current->seccomp.landlock_prog is not the answer.
Hum, I don't see the difference from a performance point of view between
a cgroup-based or a process hierarchy-based system.
Maybe a better option should be to use an array of pointers with N
entries, one for each supported hook, instead of a unique pointer list?
yes, clearly array dereference is faster than link list walk.
Now the question is where to keep this prog_array[num_lsm_hooks] ?
Since we cannot keep it inside task_struct, we have to allocate it.
Every time the task is creted then. What to do on the fork? That
will require changes all over. Then the obvious optimization would be
to share this allocated array of prog pointers across multiple tasks...
and little by little this new facility will look like cgroup.
Hence the suggestion to put this array into cgroup from the start.
I see your point :)
quoted
quoted
Anyway, being able to attach an LSM hook program to a cgroup thanks to
the new BPF_PROG_ATTACH seems a good idea (while keeping the possibility
to use a process hierarchy). The downside will be to handle an LSM hook
program which is not triggered by a seccomp-filter, but this should be
needed anyway to handle interruptions.
what do you mean 'not triggered by seccomp' ?
You're not suggesting that this lsm has to enable seccomp to be functional?
imo that's non starter due to overhead.
Yes, for now, it is triggered by a new seccomp filter return value
RET_LANDLOCK, which can take a 16-bit value called cookie. This must not
be needed but could be useful to bind a seccomp filter security policy
with a Landlock one. Waiting for Kees's point of view…
I'm not Kees, but I'd be okay with that. I still think that doing
this by process hierarchy a la seccomp will be easier to use and to
understand (which is quite important for this kind of work) than doing
it by cgroup.
A feature I've wanted to add for a while is to have an fd that
represents a seccomp layer, the idea being that you would set up your
seccomp layer (with syscall filter, landlock hooks, etc) and then you
would have a syscall to install that layer. Then an unprivileged
sandbox manager could set up its layer and still be able to inject new
processes into it later on, no cgroups needed.
--Andy
From: Andy Lutomirski <luto@amacapital.net> Date: 2016-08-28 08:15:11
On Aug 27, 2016 8:12 PM, "Alexei Starovoitov"
[off-list ref] wrote:
On Sat, Aug 27, 2016 at 12:30:36AM -0700, Andy Lutomirski wrote:
quoted
quoted
cgroup is the common way to group multiple tasks.
Without cgroup only parent<->child relationship will be possible,
which will limit usability of such lsm to a master task that controls
its children. Such api restriction would have been ok, if we could
extend it in the future, but unfortunately task-centric won't allow it
without creating a parallel lsm that is cgroup based.
Therefore I think we have to go with cgroup-centric api and your
application has to use cgroups from the start though only parent-child
would have been enough.
Also I don't think the kernel can afford two bpf based lsm. One task
based and another cgroup based, so we have to find common ground
that suits both use cases.
Having unprivliged access is a subset. There is no strong reason why
cgroup+lsm+bpf should be limited to root only always.
When we can guarantee no pointer leaks, we can allow unpriv.
I don't really understand what you mean. In the context of landlock,
which is a *sandbox*, can one of you explain a use case that
materially benefits from this type of cgroup usage? I haven't thought
of one.
In case of seccomp-like sandbox where parent controls child processes
cgroup is not needed. It's needed when container management software
needs to control a set of applications. If we can have one bpf-based lsm
that works via cgroup and without, I'd be fine with it. Right now
I haven't seen a plausible proposal to do that. Therefore cgroup based
api is a common api that works for sandbox as well, though requiring
parent to create a cgroup just to control a single child is cumbersome.
I don't believe that a common API can work to accomplish your goal.
For privileged container management, the manager is trusted. For
unprivileged sandboxing, the manager is emphatically not trusted,
which means you need special rules like NO_NEW_PRIVS, and, unless you
want to start restricting setuid and such in some cgroups, you really
do need a different interface for joining the sandbox than whatever
the container manager is using.
What could make sense is to have one BPF-based LSM that supports both
a seccomp-like unprivileged interface and a cgroup-based privileged
interface. Most of the code for it is the BPF part anyway -- all that
the cgroup or seccomp part needs to do is to figure out which BPF
program(s) to call.
Also, for container management software, you don't really need
everything tied to cgroup -- you just need a way to cleanly add new
processes to the same security context.
On Aug 27, 2016 11:14 PM, "Mickaël Salaün" [off-list ref] wrote:
quoted
On 27/08/2016 22:43, Alexei Starovoitov wrote:
quoted
On Sat, Aug 27, 2016 at 09:35:14PM +0200, Mickaël Salaün wrote:
quoted
On 27/08/2016 20:06, Alexei Starovoitov wrote:
quoted
On Sat, Aug 27, 2016 at 04:06:38PM +0200, Mickaël Salaün wrote:
quoted
As said above, Landlock will not run an eBPF programs when not strictly
needed. Attaching to a cgroup will have the same performance impact as
attaching to a process hierarchy.
Having a prog per cgroup per lsm_hook is the only scalable way I
could come up with. If you see another way, please propose.
current->seccomp.landlock_prog is not the answer.
Hum, I don't see the difference from a performance point of view between
a cgroup-based or a process hierarchy-based system.
Maybe a better option should be to use an array of pointers with N
entries, one for each supported hook, instead of a unique pointer list?
yes, clearly array dereference is faster than link list walk.
Now the question is where to keep this prog_array[num_lsm_hooks] ?
Since we cannot keep it inside task_struct, we have to allocate it.
Every time the task is creted then. What to do on the fork? That
will require changes all over. Then the obvious optimization would be
to share this allocated array of prog pointers across multiple tasks...
and little by little this new facility will look like cgroup.
Hence the suggestion to put this array into cgroup from the start.
I see your point :)
quoted
quoted
Anyway, being able to attach an LSM hook program to a cgroup thanks to
the new BPF_PROG_ATTACH seems a good idea (while keeping the possibility
to use a process hierarchy). The downside will be to handle an LSM hook
program which is not triggered by a seccomp-filter, but this should be
needed anyway to handle interruptions.
what do you mean 'not triggered by seccomp' ?
You're not suggesting that this lsm has to enable seccomp to be functional?
imo that's non starter due to overhead.
Yes, for now, it is triggered by a new seccomp filter return value
RET_LANDLOCK, which can take a 16-bit value called cookie. This must not
be needed but could be useful to bind a seccomp filter security policy
with a Landlock one. Waiting for Kees's point of view…
I'm not Kees, but I'd be okay with that. I still think that doing
this by process hierarchy a la seccomp will be easier to use and to
understand (which is quite important for this kind of work) than doing
it by cgroup.
A feature I've wanted to add for a while is to have an fd that
represents a seccomp layer, the idea being that you would set up your
seccomp layer (with syscall filter, landlock hooks, etc) and then you
would have a syscall to install that layer. Then an unprivileged
sandbox manager could set up its layer and still be able to inject new
processes into it later on, no cgroups needed.
A nice thing I didn't highlight about Landlock is that a process can
prepare a layer of rules (arraymap of handles + Landlock programs) and
pass the file descriptors of the Landlock programs to another process.
This process could then apply this programs to get sandboxed. However,
for now, because a Landlock program is only triggered by a seccomp
filter (which do not follow the Landlock programs as a FD), they will be
useless.
The FD referring to an arraymap of handles can also be used to update a
map and change the behavior of a Landlock program. A master process can
then add or remove restrictions to another process hierarchy on the fly.
However, I think it would make more sense to use cgroups if we want to
move an existing (unwilling) unsandoxed process into a sandboxed
environment. Of course, some more no_new_privs checks would be needed.
From: Andy Lutomirski <luto@amacapital.net> Date: 2016-08-30 16:06:54
On Thu, Aug 25, 2016 at 3:32 AM, Mickaël Salaün [off-list ref] wrote:
Hi,
This series is a proof of concept to fill some missing part of seccomp as the
ability to check syscall argument pointers or creating more dynamic security
policies. The goal of this new stackable Linux Security Module (LSM) called
Landlock is to allow any process, including unprivileged ones, to create
powerful security sandboxes comparable to the Seatbelt/XNU Sandbox or the
OpenBSD Pledge. This kind of sandbox help to mitigate the security impact of
bugs or unexpected/malicious behaviors in userland applications.
From: Andy Lutomirski <luto@amacapital.net> Date: 2016-08-30 18:55:40
On Sun, Aug 28, 2016 at 2:42 AM, Mickaël Salaün [off-list ref] wrote:
On 28/08/2016 10:13, Andy Lutomirski wrote:
quoted
On Aug 27, 2016 11:14 PM, "Mickaël Salaün" [off-list ref] wrote:
quoted
On 27/08/2016 22:43, Alexei Starovoitov wrote:
quoted
On Sat, Aug 27, 2016 at 09:35:14PM +0200, Mickaël Salaün wrote:
quoted
On 27/08/2016 20:06, Alexei Starovoitov wrote:
quoted
On Sat, Aug 27, 2016 at 04:06:38PM +0200, Mickaël Salaün wrote:
quoted
As said above, Landlock will not run an eBPF programs when not strictly
needed. Attaching to a cgroup will have the same performance impact as
attaching to a process hierarchy.
Having a prog per cgroup per lsm_hook is the only scalable way I
could come up with. If you see another way, please propose.
current->seccomp.landlock_prog is not the answer.
Hum, I don't see the difference from a performance point of view between
a cgroup-based or a process hierarchy-based system.
Maybe a better option should be to use an array of pointers with N
entries, one for each supported hook, instead of a unique pointer list?
yes, clearly array dereference is faster than link list walk.
Now the question is where to keep this prog_array[num_lsm_hooks] ?
Since we cannot keep it inside task_struct, we have to allocate it.
Every time the task is creted then. What to do on the fork? That
will require changes all over. Then the obvious optimization would be
to share this allocated array of prog pointers across multiple tasks...
and little by little this new facility will look like cgroup.
Hence the suggestion to put this array into cgroup from the start.
I see your point :)
quoted
quoted
Anyway, being able to attach an LSM hook program to a cgroup thanks to
the new BPF_PROG_ATTACH seems a good idea (while keeping the possibility
to use a process hierarchy). The downside will be to handle an LSM hook
program which is not triggered by a seccomp-filter, but this should be
needed anyway to handle interruptions.
what do you mean 'not triggered by seccomp' ?
You're not suggesting that this lsm has to enable seccomp to be functional?
imo that's non starter due to overhead.
Yes, for now, it is triggered by a new seccomp filter return value
RET_LANDLOCK, which can take a 16-bit value called cookie. This must not
be needed but could be useful to bind a seccomp filter security policy
with a Landlock one. Waiting for Kees's point of view…
I'm not Kees, but I'd be okay with that. I still think that doing
this by process hierarchy a la seccomp will be easier to use and to
understand (which is quite important for this kind of work) than doing
it by cgroup.
A feature I've wanted to add for a while is to have an fd that
represents a seccomp layer, the idea being that you would set up your
seccomp layer (with syscall filter, landlock hooks, etc) and then you
would have a syscall to install that layer. Then an unprivileged
sandbox manager could set up its layer and still be able to inject new
processes into it later on, no cgroups needed.
A nice thing I didn't highlight about Landlock is that a process can
prepare a layer of rules (arraymap of handles + Landlock programs) and
pass the file descriptors of the Landlock programs to another process.
This process could then apply this programs to get sandboxed. However,
for now, because a Landlock program is only triggered by a seccomp
filter (which do not follow the Landlock programs as a FD), they will be
useless.
The FD referring to an arraymap of handles can also be used to update a
map and change the behavior of a Landlock program. A master process can
then add or remove restrictions to another process hierarchy on the fly.
Maybe this could be extended a little bit. The fd could hold the
seccomp filter *and* the LSM hook filters. FMODE_EXECUTE could give
the ability to install it and FMODE_WRITE could give the ability to
modify it.
From: Andy Lutomirski <luto@amacapital.net> Date: 2016-08-30 18:57:00
On Aug 25, 2016 12:34 PM, "Mickaël Salaün" [off-list ref] wrote:
Add LSM hooks which can be used by userland through Landlock (eBPF)
programs. This programs are limited to a whitelist of functions (cf.
next commit). The eBPF program context is depicted by the struct
landlock_data (cf. include/uapi/linux/bpf.h):
* hook: LSM hook ID (useful when using the same program for multiple LSM
hooks);
* cookie: the 16-bit value from the seccomp filter that triggered this
Landlock program;
* args[6]: array of LSM hook arguments.
The LSM hook arguments can contain raw values as integers or
(unleakable) pointers. The only way to use the pointers are to pass them
to an eBPF function according to their types (e.g. the
bpf_landlock_cmp_fs_beneath_with_struct_file function can use a struct
file pointer).
For now, there is three hooks for file system access control:
* file_open;
* file_permission;
* mmap_file.
What's the purpose of exposing struct cred * to userspace? It's
primarily just an optimization to save a bit of RAM, and it's a
dubious optimization at that. What are you using it for? Would it
make more sense to use struct task_struct * or struct pid * instead?
Also, exposing struct cred * has a really weird side-effect: it allows
(maybe even encourages) checking for pointer equality between two
struct cred * objects. Doing so will have erratic results.
On Thu, Aug 25, 2016 at 3:32 AM, Mickaël Salaün [off-list ref] wrote:
quoted
Hi,
This series is a proof of concept to fill some missing part of seccomp as the
ability to check syscall argument pointers or creating more dynamic security
policies. The goal of this new stackable Linux Security Module (LSM) called
Landlock is to allow any process, including unprivileged ones, to create
powerful security sandboxes comparable to the Seatbelt/XNU Sandbox or the
OpenBSD Pledge. This kind of sandbox help to mitigate the security impact of
bugs or unexpected/malicious behaviors in userland applications.
Mickaël, will you be at KS and/or LPC?
I won't be at KS/LPC but I will give a talk at Kernel Recipes (Paris)
for which registration will start Thursday (and will not last long). :)
Mickaël
From: Andy Lutomirski <luto@amacapital.net> Date: 2016-08-30 19:55:31
On Tue, Aug 30, 2016 at 12:51 PM, Mickaël Salaün [off-list ref] wrote:
On 30/08/2016 18:06, Andy Lutomirski wrote:
quoted
On Thu, Aug 25, 2016 at 3:32 AM, Mickaël Salaün [off-list ref] wrote:
quoted
Hi,
This series is a proof of concept to fill some missing part of seccomp as the
ability to check syscall argument pointers or creating more dynamic security
policies. The goal of this new stackable Linux Security Module (LSM) called
Landlock is to allow any process, including unprivileged ones, to create
powerful security sandboxes comparable to the Seatbelt/XNU Sandbox or the
OpenBSD Pledge. This kind of sandbox help to mitigate the security impact of
bugs or unexpected/malicious behaviors in userland applications.
Mickaël, will you be at KS and/or LPC?
I won't be at KS/LPC but I will give a talk at Kernel Recipes (Paris)
for which registration will start Thursday (and will not last long). :)
There's a teeny tiny chance I'll be there. I've done way too much
traveling lately.
On Aug 25, 2016 12:34 PM, "Mickaël Salaün" [off-list ref] wrote:
quoted
Add LSM hooks which can be used by userland through Landlock (eBPF)
programs. This programs are limited to a whitelist of functions (cf.
next commit). The eBPF program context is depicted by the struct
landlock_data (cf. include/uapi/linux/bpf.h):
* hook: LSM hook ID (useful when using the same program for multiple LSM
hooks);
* cookie: the 16-bit value from the seccomp filter that triggered this
Landlock program;
* args[6]: array of LSM hook arguments.
The LSM hook arguments can contain raw values as integers or
(unleakable) pointers. The only way to use the pointers are to pass them
to an eBPF function according to their types (e.g. the
bpf_landlock_cmp_fs_beneath_with_struct_file function can use a struct
file pointer).
For now, there is three hooks for file system access control:
* file_open;
* file_permission;
* mmap_file.
What's the purpose of exposing struct cred * to userspace? It's
primarily just an optimization to save a bit of RAM, and it's a
dubious optimization at that. What are you using it for? Would it
make more sense to use struct task_struct * or struct pid * instead?
Also, exposing struct cred * has a really weird side-effect: it allows
(maybe even encourages) checking for pointer equality between two
struct cred * objects. Doing so will have erratic results.
The pointers exposed in the ePBF context are not directly readable by an
unprivileged eBPF program thanks to the strong typing of the Landlock
context and the static eBPF verification. There is no way to leak a
kernel pointer to userspace from an unprivileged eBPF program: pointer
arithmetic and comparison are prohibited. Pointers can only be pass as
argument to dedicated eBPF functions.
For now, struct cred * is simply not used by any eBPF function and then
not usable at all. It only exist here because I map the LSM hook
arguments in a generic/automatic way to the eBPF context.
I'm planning to extend the Landlock context with extra pointers,
whatever the LSM hook. We could then use task_struct, skb or any other
kernel objects, in a safe way, with dedicated functions.
From: Andy Lutomirski <luto@amacapital.net> Date: 2016-08-30 20:19:23
On Tue, Aug 30, 2016 at 1:10 PM, Mickaël Salaün [off-list ref] wrote:
On 30/08/2016 20:56, Andy Lutomirski wrote:
quoted
On Aug 25, 2016 12:34 PM, "Mickaël Salaün" [off-list ref] wrote:
quoted
Add LSM hooks which can be used by userland through Landlock (eBPF)
programs. This programs are limited to a whitelist of functions (cf.
next commit). The eBPF program context is depicted by the struct
landlock_data (cf. include/uapi/linux/bpf.h):
* hook: LSM hook ID (useful when using the same program for multiple LSM
hooks);
* cookie: the 16-bit value from the seccomp filter that triggered this
Landlock program;
* args[6]: array of LSM hook arguments.
The LSM hook arguments can contain raw values as integers or
(unleakable) pointers. The only way to use the pointers are to pass them
to an eBPF function according to their types (e.g. the
bpf_landlock_cmp_fs_beneath_with_struct_file function can use a struct
file pointer).
For now, there is three hooks for file system access control:
* file_open;
* file_permission;
* mmap_file.
What's the purpose of exposing struct cred * to userspace? It's
primarily just an optimization to save a bit of RAM, and it's a
dubious optimization at that. What are you using it for? Would it
make more sense to use struct task_struct * or struct pid * instead?
Also, exposing struct cred * has a really weird side-effect: it allows
(maybe even encourages) checking for pointer equality between two
struct cred * objects. Doing so will have erratic results.
The pointers exposed in the ePBF context are not directly readable by an
unprivileged eBPF program thanks to the strong typing of the Landlock
context and the static eBPF verification. There is no way to leak a
kernel pointer to userspace from an unprivileged eBPF program: pointer
arithmetic and comparison are prohibited. Pointers can only be pass as
argument to dedicated eBPF functions.
I'm not talking about leaking the value -- I'm talking about leaking
the predicate (a == b) for two struct cred pointers. That predicate
shouldn't be available because it has very odd effects.
For now, struct cred * is simply not used by any eBPF function and then
not usable at all. It only exist here because I map the LSM hook
arguments in a generic/automatic way to the eBPF context.
On Sun, Aug 28, 2016 at 2:42 AM, Mickaël Salaün [off-list ref] wrote:
quoted
On 28/08/2016 10:13, Andy Lutomirski wrote:
quoted
On Aug 27, 2016 11:14 PM, "Mickaël Salaün" [off-list ref] wrote:
quoted
On 27/08/2016 22:43, Alexei Starovoitov wrote:
quoted
On Sat, Aug 27, 2016 at 09:35:14PM +0200, Mickaël Salaün wrote:
quoted
On 27/08/2016 20:06, Alexei Starovoitov wrote:
quoted
On Sat, Aug 27, 2016 at 04:06:38PM +0200, Mickaël Salaün wrote:
quoted
As said above, Landlock will not run an eBPF programs when not strictly
needed. Attaching to a cgroup will have the same performance impact as
attaching to a process hierarchy.
Having a prog per cgroup per lsm_hook is the only scalable way I
could come up with. If you see another way, please propose.
current->seccomp.landlock_prog is not the answer.
Hum, I don't see the difference from a performance point of view between
a cgroup-based or a process hierarchy-based system.
Maybe a better option should be to use an array of pointers with N
entries, one for each supported hook, instead of a unique pointer list?
yes, clearly array dereference is faster than link list walk.
Now the question is where to keep this prog_array[num_lsm_hooks] ?
Since we cannot keep it inside task_struct, we have to allocate it.
Every time the task is creted then. What to do on the fork? That
will require changes all over. Then the obvious optimization would be
to share this allocated array of prog pointers across multiple tasks...
and little by little this new facility will look like cgroup.
Hence the suggestion to put this array into cgroup from the start.
I see your point :)
quoted
quoted
Anyway, being able to attach an LSM hook program to a cgroup thanks to
the new BPF_PROG_ATTACH seems a good idea (while keeping the possibility
to use a process hierarchy). The downside will be to handle an LSM hook
program which is not triggered by a seccomp-filter, but this should be
needed anyway to handle interruptions.
what do you mean 'not triggered by seccomp' ?
You're not suggesting that this lsm has to enable seccomp to be functional?
imo that's non starter due to overhead.
Yes, for now, it is triggered by a new seccomp filter return value
RET_LANDLOCK, which can take a 16-bit value called cookie. This must not
be needed but could be useful to bind a seccomp filter security policy
with a Landlock one. Waiting for Kees's point of view…
I'm not Kees, but I'd be okay with that. I still think that doing
this by process hierarchy a la seccomp will be easier to use and to
understand (which is quite important for this kind of work) than doing
it by cgroup.
A feature I've wanted to add for a while is to have an fd that
represents a seccomp layer, the idea being that you would set up your
seccomp layer (with syscall filter, landlock hooks, etc) and then you
would have a syscall to install that layer. Then an unprivileged
sandbox manager could set up its layer and still be able to inject new
processes into it later on, no cgroups needed.
A nice thing I didn't highlight about Landlock is that a process can
prepare a layer of rules (arraymap of handles + Landlock programs) and
pass the file descriptors of the Landlock programs to another process.
This process could then apply this programs to get sandboxed. However,
for now, because a Landlock program is only triggered by a seccomp
filter (which do not follow the Landlock programs as a FD), they will be
useless.
The FD referring to an arraymap of handles can also be used to update a
map and change the behavior of a Landlock program. A master process can
then add or remove restrictions to another process hierarchy on the fly.
Maybe this could be extended a little bit. The fd could hold the
seccomp filter *and* the LSM hook filters. FMODE_EXECUTE could give
the ability to install it and FMODE_WRITE could give the ability to
modify it.
This is interesting! It should be possible to append the seccomp stack
of a source process to the seccomp stack of the target process when a
Landlock program is passed and then activated through seccomp(2).
For the FMODE_EXECUTE/FMODE_WRITE, are you suggesting to manage
permission of the eBPF program FD in a specific way?
From: Andy Lutomirski <luto@amacapital.net> Date: 2016-08-30 20:24:11
On Tue, Aug 30, 2016 at 1:20 PM, Mickaël Salaün [off-list ref] wrote:
On 30/08/2016 20:55, Andy Lutomirski wrote:
quoted
On Sun, Aug 28, 2016 at 2:42 AM, Mickaël Salaün [off-list ref] wrote:
quoted
On 28/08/2016 10:13, Andy Lutomirski wrote:
quoted
On Aug 27, 2016 11:14 PM, "Mickaël Salaün" [off-list ref] wrote:
quoted
On 27/08/2016 22:43, Alexei Starovoitov wrote:
quoted
On Sat, Aug 27, 2016 at 09:35:14PM +0200, Mickaël Salaün wrote:
quoted
On 27/08/2016 20:06, Alexei Starovoitov wrote:
quoted
On Sat, Aug 27, 2016 at 04:06:38PM +0200, Mickaël Salaün wrote:
quoted
As said above, Landlock will not run an eBPF programs when not strictly
needed. Attaching to a cgroup will have the same performance impact as
attaching to a process hierarchy.
Having a prog per cgroup per lsm_hook is the only scalable way I
could come up with. If you see another way, please propose.
current->seccomp.landlock_prog is not the answer.
Hum, I don't see the difference from a performance point of view between
a cgroup-based or a process hierarchy-based system.
Maybe a better option should be to use an array of pointers with N
entries, one for each supported hook, instead of a unique pointer list?
yes, clearly array dereference is faster than link list walk.
Now the question is where to keep this prog_array[num_lsm_hooks] ?
Since we cannot keep it inside task_struct, we have to allocate it.
Every time the task is creted then. What to do on the fork? That
will require changes all over. Then the obvious optimization would be
to share this allocated array of prog pointers across multiple tasks...
and little by little this new facility will look like cgroup.
Hence the suggestion to put this array into cgroup from the start.
I see your point :)
quoted
quoted
Anyway, being able to attach an LSM hook program to a cgroup thanks to
the new BPF_PROG_ATTACH seems a good idea (while keeping the possibility
to use a process hierarchy). The downside will be to handle an LSM hook
program which is not triggered by a seccomp-filter, but this should be
needed anyway to handle interruptions.
what do you mean 'not triggered by seccomp' ?
You're not suggesting that this lsm has to enable seccomp to be functional?
imo that's non starter due to overhead.
Yes, for now, it is triggered by a new seccomp filter return value
RET_LANDLOCK, which can take a 16-bit value called cookie. This must not
be needed but could be useful to bind a seccomp filter security policy
with a Landlock one. Waiting for Kees's point of view…
I'm not Kees, but I'd be okay with that. I still think that doing
this by process hierarchy a la seccomp will be easier to use and to
understand (which is quite important for this kind of work) than doing
it by cgroup.
A feature I've wanted to add for a while is to have an fd that
represents a seccomp layer, the idea being that you would set up your
seccomp layer (with syscall filter, landlock hooks, etc) and then you
would have a syscall to install that layer. Then an unprivileged
sandbox manager could set up its layer and still be able to inject new
processes into it later on, no cgroups needed.
A nice thing I didn't highlight about Landlock is that a process can
prepare a layer of rules (arraymap of handles + Landlock programs) and
pass the file descriptors of the Landlock programs to another process.
This process could then apply this programs to get sandboxed. However,
for now, because a Landlock program is only triggered by a seccomp
filter (which do not follow the Landlock programs as a FD), they will be
useless.
The FD referring to an arraymap of handles can also be used to update a
map and change the behavior of a Landlock program. A master process can
then add or remove restrictions to another process hierarchy on the fly.
Maybe this could be extended a little bit. The fd could hold the
seccomp filter *and* the LSM hook filters. FMODE_EXECUTE could give
the ability to install it and FMODE_WRITE could give the ability to
modify it.
This is interesting! It should be possible to append the seccomp stack
of a source process to the seccomp stack of the target process when a
Landlock program is passed and then activated through seccomp(2).
For the FMODE_EXECUTE/FMODE_WRITE, are you suggesting to manage
permission of the eBPF program FD in a specific way?
This wouldn't be an eBPF program FD -- it would be an FD encapsulating
an entire configuration including seccomp BPF program, whatever
landlock stuff is associated, and eventual seccomp monitor
configuration (once I write that code), etc.
You wouldn't say "attach this process's seccomp stack to me" -- you'd
say "attach this seccomp layer to me".
A decision that we'd have to make would be whether the FD links to the
parent layer or whether it can be attached without regard to what the
parent layer is.
--Andy
On Tue, Aug 30, 2016 at 1:10 PM, Mickaël Salaün [off-list ref] wrote:
quoted
On 30/08/2016 20:56, Andy Lutomirski wrote:
quoted
On Aug 25, 2016 12:34 PM, "Mickaël Salaün" [off-list ref] wrote:
quoted
Add LSM hooks which can be used by userland through Landlock (eBPF)
programs. This programs are limited to a whitelist of functions (cf.
next commit). The eBPF program context is depicted by the struct
landlock_data (cf. include/uapi/linux/bpf.h):
* hook: LSM hook ID (useful when using the same program for multiple LSM
hooks);
* cookie: the 16-bit value from the seccomp filter that triggered this
Landlock program;
* args[6]: array of LSM hook arguments.
The LSM hook arguments can contain raw values as integers or
(unleakable) pointers. The only way to use the pointers are to pass them
to an eBPF function according to their types (e.g. the
bpf_landlock_cmp_fs_beneath_with_struct_file function can use a struct
file pointer).
For now, there is three hooks for file system access control:
* file_open;
* file_permission;
* mmap_file.
What's the purpose of exposing struct cred * to userspace? It's
primarily just an optimization to save a bit of RAM, and it's a
dubious optimization at that. What are you using it for? Would it
make more sense to use struct task_struct * or struct pid * instead?
Also, exposing struct cred * has a really weird side-effect: it allows
(maybe even encourages) checking for pointer equality between two
struct cred * objects. Doing so will have erratic results.
The pointers exposed in the ePBF context are not directly readable by an
unprivileged eBPF program thanks to the strong typing of the Landlock
context and the static eBPF verification. There is no way to leak a
kernel pointer to userspace from an unprivileged eBPF program: pointer
arithmetic and comparison are prohibited. Pointers can only be pass as
argument to dedicated eBPF functions.
I'm not talking about leaking the value -- I'm talking about leaking
the predicate (a == b) for two struct cred pointers. That predicate
shouldn't be available because it has very odd effects.
I'm pretty sure this case is covered with the impossibility of doing
pointers comparison.
quoted
For now, struct cred * is simply not used by any eBPF function and then
not usable at all. It only exist here because I map the LSM hook
arguments in a generic/automatic way to the eBPF context.
Maybe remove it from this patch set then?
Well, this is done with the LANDLOCK_HOOK* macros but I will remove it.
On Tue, Aug 30, 2016 at 1:20 PM, Mickaël Salaün [off-list ref] wrote:
quoted
On 30/08/2016 20:55, Andy Lutomirski wrote:
quoted
On Sun, Aug 28, 2016 at 2:42 AM, Mickaël Salaün [off-list ref] wrote:
quoted
On 28/08/2016 10:13, Andy Lutomirski wrote:
quoted
On Aug 27, 2016 11:14 PM, "Mickaël Salaün" [off-list ref] wrote:
quoted
On 27/08/2016 22:43, Alexei Starovoitov wrote:
quoted
On Sat, Aug 27, 2016 at 09:35:14PM +0200, Mickaël Salaün wrote:
quoted
On 27/08/2016 20:06, Alexei Starovoitov wrote:
quoted
On Sat, Aug 27, 2016 at 04:06:38PM +0200, Mickaël Salaün wrote:
quoted
As said above, Landlock will not run an eBPF programs when not strictly
needed. Attaching to a cgroup will have the same performance impact as
attaching to a process hierarchy.
Having a prog per cgroup per lsm_hook is the only scalable way I
could come up with. If you see another way, please propose.
current->seccomp.landlock_prog is not the answer.
Hum, I don't see the difference from a performance point of view between
a cgroup-based or a process hierarchy-based system.
Maybe a better option should be to use an array of pointers with N
entries, one for each supported hook, instead of a unique pointer list?
yes, clearly array dereference is faster than link list walk.
Now the question is where to keep this prog_array[num_lsm_hooks] ?
Since we cannot keep it inside task_struct, we have to allocate it.
Every time the task is creted then. What to do on the fork? That
will require changes all over. Then the obvious optimization would be
to share this allocated array of prog pointers across multiple tasks...
and little by little this new facility will look like cgroup.
Hence the suggestion to put this array into cgroup from the start.
I see your point :)
quoted
quoted
Anyway, being able to attach an LSM hook program to a cgroup thanks to
the new BPF_PROG_ATTACH seems a good idea (while keeping the possibility
to use a process hierarchy). The downside will be to handle an LSM hook
program which is not triggered by a seccomp-filter, but this should be
needed anyway to handle interruptions.
what do you mean 'not triggered by seccomp' ?
You're not suggesting that this lsm has to enable seccomp to be functional?
imo that's non starter due to overhead.
Yes, for now, it is triggered by a new seccomp filter return value
RET_LANDLOCK, which can take a 16-bit value called cookie. This must not
be needed but could be useful to bind a seccomp filter security policy
with a Landlock one. Waiting for Kees's point of view…
I'm not Kees, but I'd be okay with that. I still think that doing
this by process hierarchy a la seccomp will be easier to use and to
understand (which is quite important for this kind of work) than doing
it by cgroup.
A feature I've wanted to add for a while is to have an fd that
represents a seccomp layer, the idea being that you would set up your
seccomp layer (with syscall filter, landlock hooks, etc) and then you
would have a syscall to install that layer. Then an unprivileged
sandbox manager could set up its layer and still be able to inject new
processes into it later on, no cgroups needed.
A nice thing I didn't highlight about Landlock is that a process can
prepare a layer of rules (arraymap of handles + Landlock programs) and
pass the file descriptors of the Landlock programs to another process.
This process could then apply this programs to get sandboxed. However,
for now, because a Landlock program is only triggered by a seccomp
filter (which do not follow the Landlock programs as a FD), they will be
useless.
The FD referring to an arraymap of handles can also be used to update a
map and change the behavior of a Landlock program. A master process can
then add or remove restrictions to another process hierarchy on the fly.
Maybe this could be extended a little bit. The fd could hold the
seccomp filter *and* the LSM hook filters. FMODE_EXECUTE could give
the ability to install it and FMODE_WRITE could give the ability to
modify it.
This is interesting! It should be possible to append the seccomp stack
of a source process to the seccomp stack of the target process when a
Landlock program is passed and then activated through seccomp(2).
For the FMODE_EXECUTE/FMODE_WRITE, are you suggesting to manage
permission of the eBPF program FD in a specific way?
This wouldn't be an eBPF program FD -- it would be an FD encapsulating
an entire configuration including seccomp BPF program, whatever
landlock stuff is associated, and eventual seccomp monitor
configuration (once I write that code), etc.
You wouldn't say "attach this process's seccomp stack to me" -- you'd
say "attach this seccomp layer to me".
A decision that we'd have to make would be whether the FD links to the
parent layer or whether it can be attached without regard to what the
parent layer is.
OK, I like that, but I think it could be done on a second time. :)
On Tue, Aug 30, 2016 at 10:33:31PM +0200, Mickaël Salaün wrote:
On 30/08/2016 22:23, Andy Lutomirski wrote:
quoted
On Tue, Aug 30, 2016 at 1:20 PM, Mickaël Salaün [off-list ref] wrote:
quoted
On 30/08/2016 20:55, Andy Lutomirski wrote:
quoted
On Sun, Aug 28, 2016 at 2:42 AM, Mickaël Salaün [off-list ref] wrote:
quoted
On 28/08/2016 10:13, Andy Lutomirski wrote:
quoted
On Aug 27, 2016 11:14 PM, "Mickaël Salaün" [off-list ref] wrote:
quoted
On 27/08/2016 22:43, Alexei Starovoitov wrote:
quoted
On Sat, Aug 27, 2016 at 09:35:14PM +0200, Mickaël Salaün wrote:
quoted
On 27/08/2016 20:06, Alexei Starovoitov wrote:
quoted
On Sat, Aug 27, 2016 at 04:06:38PM +0200, Mickaël Salaün wrote:
quoted
As said above, Landlock will not run an eBPF programs when not strictly
needed. Attaching to a cgroup will have the same performance impact as
attaching to a process hierarchy.
Having a prog per cgroup per lsm_hook is the only scalable way I
could come up with. If you see another way, please propose.
current->seccomp.landlock_prog is not the answer.
Hum, I don't see the difference from a performance point of view between
a cgroup-based or a process hierarchy-based system.
Maybe a better option should be to use an array of pointers with N
entries, one for each supported hook, instead of a unique pointer list?
yes, clearly array dereference is faster than link list walk.
Now the question is where to keep this prog_array[num_lsm_hooks] ?
Since we cannot keep it inside task_struct, we have to allocate it.
Every time the task is creted then. What to do on the fork? That
will require changes all over. Then the obvious optimization would be
to share this allocated array of prog pointers across multiple tasks...
and little by little this new facility will look like cgroup.
Hence the suggestion to put this array into cgroup from the start.
I see your point :)
quoted
quoted
Anyway, being able to attach an LSM hook program to a cgroup thanks to
the new BPF_PROG_ATTACH seems a good idea (while keeping the possibility
to use a process hierarchy). The downside will be to handle an LSM hook
program which is not triggered by a seccomp-filter, but this should be
needed anyway to handle interruptions.
what do you mean 'not triggered by seccomp' ?
You're not suggesting that this lsm has to enable seccomp to be functional?
imo that's non starter due to overhead.
Yes, for now, it is triggered by a new seccomp filter return value
RET_LANDLOCK, which can take a 16-bit value called cookie. This must not
be needed but could be useful to bind a seccomp filter security policy
with a Landlock one. Waiting for Kees's point of view…
I'm not Kees, but I'd be okay with that. I still think that doing
this by process hierarchy a la seccomp will be easier to use and to
understand (which is quite important for this kind of work) than doing
it by cgroup.
A feature I've wanted to add for a while is to have an fd that
represents a seccomp layer, the idea being that you would set up your
seccomp layer (with syscall filter, landlock hooks, etc) and then you
would have a syscall to install that layer. Then an unprivileged
sandbox manager could set up its layer and still be able to inject new
processes into it later on, no cgroups needed.
A nice thing I didn't highlight about Landlock is that a process can
prepare a layer of rules (arraymap of handles + Landlock programs) and
pass the file descriptors of the Landlock programs to another process.
This process could then apply this programs to get sandboxed. However,
for now, because a Landlock program is only triggered by a seccomp
filter (which do not follow the Landlock programs as a FD), they will be
useless.
The FD referring to an arraymap of handles can also be used to update a
map and change the behavior of a Landlock program. A master process can
then add or remove restrictions to another process hierarchy on the fly.
Maybe this could be extended a little bit. The fd could hold the
seccomp filter *and* the LSM hook filters. FMODE_EXECUTE could give
the ability to install it and FMODE_WRITE could give the ability to
modify it.
This is interesting! It should be possible to append the seccomp stack
of a source process to the seccomp stack of the target process when a
Landlock program is passed and then activated through seccomp(2).
For the FMODE_EXECUTE/FMODE_WRITE, are you suggesting to manage
permission of the eBPF program FD in a specific way?
This wouldn't be an eBPF program FD -- it would be an FD encapsulating
an entire configuration including seccomp BPF program, whatever
landlock stuff is associated, and eventual seccomp monitor
configuration (once I write that code), etc.
You wouldn't say "attach this process's seccomp stack to me" -- you'd
say "attach this seccomp layer to me".
A decision that we'd have to make would be whether the FD links to the
parent layer or whether it can be attached without regard to what the
parent layer is.
OK, I like that, but I think it could be done on a second time. :)
I don't. Single FD that is a collection of objects seems an odd abstraction
to me. I also don't see what it actually solves.
I think lsm and seccomp should be orthogonal and not tied into each other.
From: Andy Lutomirski <luto@amacapital.net> Date: 2016-08-30 21:45:43
On Aug 30, 2016 1:56 PM, "Alexei Starovoitov"
[off-list ref] wrote:
On Tue, Aug 30, 2016 at 10:33:31PM +0200, Mickaël Salaün wrote:
quoted
On 30/08/2016 22:23, Andy Lutomirski wrote:
quoted
On Tue, Aug 30, 2016 at 1:20 PM, Mickaël Salaün [off-list ref] wrote:
quoted
On 30/08/2016 20:55, Andy Lutomirski wrote:
quoted
On Sun, Aug 28, 2016 at 2:42 AM, Mickaël Salaün [off-list ref] wrote:
quoted
On 28/08/2016 10:13, Andy Lutomirski wrote:
quoted
On Aug 27, 2016 11:14 PM, "Mickaël Salaün" [off-list ref] wrote:
quoted
On 27/08/2016 22:43, Alexei Starovoitov wrote:
quoted
On Sat, Aug 27, 2016 at 09:35:14PM +0200, Mickaël Salaün wrote:
quoted
On 27/08/2016 20:06, Alexei Starovoitov wrote:
quoted
On Sat, Aug 27, 2016 at 04:06:38PM +0200, Mickaël Salaün wrote:
quoted
As said above, Landlock will not run an eBPF programs when not strictly
needed. Attaching to a cgroup will have the same performance impact as
attaching to a process hierarchy.
Having a prog per cgroup per lsm_hook is the only scalable way I
could come up with. If you see another way, please propose.
current->seccomp.landlock_prog is not the answer.
Hum, I don't see the difference from a performance point of view between
a cgroup-based or a process hierarchy-based system.
Maybe a better option should be to use an array of pointers with N
entries, one for each supported hook, instead of a unique pointer list?
yes, clearly array dereference is faster than link list walk.
Now the question is where to keep this prog_array[num_lsm_hooks] ?
Since we cannot keep it inside task_struct, we have to allocate it.
Every time the task is creted then. What to do on the fork? That
will require changes all over. Then the obvious optimization would be
to share this allocated array of prog pointers across multiple tasks...
and little by little this new facility will look like cgroup.
Hence the suggestion to put this array into cgroup from the start.
I see your point :)
quoted
quoted
Anyway, being able to attach an LSM hook program to a cgroup thanks to
the new BPF_PROG_ATTACH seems a good idea (while keeping the possibility
to use a process hierarchy). The downside will be to handle an LSM hook
program which is not triggered by a seccomp-filter, but this should be
needed anyway to handle interruptions.
what do you mean 'not triggered by seccomp' ?
You're not suggesting that this lsm has to enable seccomp to be functional?
imo that's non starter due to overhead.
Yes, for now, it is triggered by a new seccomp filter return value
RET_LANDLOCK, which can take a 16-bit value called cookie. This must not
be needed but could be useful to bind a seccomp filter security policy
with a Landlock one. Waiting for Kees's point of view…
I'm not Kees, but I'd be okay with that. I still think that doing
this by process hierarchy a la seccomp will be easier to use and to
understand (which is quite important for this kind of work) than doing
it by cgroup.
A feature I've wanted to add for a while is to have an fd that
represents a seccomp layer, the idea being that you would set up your
seccomp layer (with syscall filter, landlock hooks, etc) and then you
would have a syscall to install that layer. Then an unprivileged
sandbox manager could set up its layer and still be able to inject new
processes into it later on, no cgroups needed.
A nice thing I didn't highlight about Landlock is that a process can
prepare a layer of rules (arraymap of handles + Landlock programs) and
pass the file descriptors of the Landlock programs to another process.
This process could then apply this programs to get sandboxed. However,
for now, because a Landlock program is only triggered by a seccomp
filter (which do not follow the Landlock programs as a FD), they will be
useless.
The FD referring to an arraymap of handles can also be used to update a
map and change the behavior of a Landlock program. A master process can
then add or remove restrictions to another process hierarchy on the fly.
Maybe this could be extended a little bit. The fd could hold the
seccomp filter *and* the LSM hook filters. FMODE_EXECUTE could give
the ability to install it and FMODE_WRITE could give the ability to
modify it.
This is interesting! It should be possible to append the seccomp stack
of a source process to the seccomp stack of the target process when a
Landlock program is passed and then activated through seccomp(2).
For the FMODE_EXECUTE/FMODE_WRITE, are you suggesting to manage
permission of the eBPF program FD in a specific way?
This wouldn't be an eBPF program FD -- it would be an FD encapsulating
an entire configuration including seccomp BPF program, whatever
landlock stuff is associated, and eventual seccomp monitor
configuration (once I write that code), etc.
You wouldn't say "attach this process's seccomp stack to me" -- you'd
say "attach this seccomp layer to me".
A decision that we'd have to make would be whether the FD links to the
parent layer or whether it can be attached without regard to what the
parent layer is.
OK, I like that, but I think it could be done on a second time. :)
I don't. Single FD that is a collection of objects seems an odd abstraction
to me. I also don't see what it actually solves.
I think lsm and seccomp should be orthogonal and not tied into each other.
It's not a random collection of objects. It's a fully configured
sandboxing layer.
One might argue that landlock shouldn't be tied to seccomp (in theory,
attached progs could be given access to syscall_get_xyz()), but I
think that the seccomp attachment mechanism is the right way to
install unprivileged filters. It handles the no_new_privs stuff, it
allows TSYNC, it's totally independent of systemwide policy, etc.
Trying to use cgroups or similar for this is going to be much nastier.
Some tighter sandboxes (Sandstorm, etc) aren't even going to dream of
putting cgroupfs in their containers, so requiring cgroups or similar
would be a mess for that type of application.
--Andy
On Tue, Aug 30, 2016 at 02:45:14PM -0700, Andy Lutomirski wrote:
One might argue that landlock shouldn't be tied to seccomp (in theory,
attached progs could be given access to syscall_get_xyz()), but I
proposed lsm is way more powerful than syscall_get_xyz.
no need to dumb it down.
think that the seccomp attachment mechanism is the right way to
install unprivileged filters. It handles the no_new_privs stuff, it
allows TSYNC, it's totally independent of systemwide policy, etc.
Trying to use cgroups or similar for this is going to be much nastier.
Some tighter sandboxes (Sandstorm, etc) aren't even going to dream of
putting cgroupfs in their containers, so requiring cgroups or similar
would be a mess for that type of application.
I don't see why it is a 'mess'. cgroups are already used by majority
of the systems, so I don't see why requiring a cgroup is such a big deal.
But let's say we don't do them. How implementation is going to look like
for task based hierarchy? Note that we need an array of bpf_prog pointers.
One for each lsm hook. Where this array is going to be stored?
We cannot put in task_struct, since it's too large. Cannot put it
into 'struct seccomp' directly either, unless it will become a pointer.
Is that the proposal?
So now we will be wasting extra 1kbyte of memory per task. Not great.
We'd want to optimize it by sharing this such struct seccomp with prog array
across threads of the same task? Or dynimically allocating it when
landlock is in use? May sound nice, but how to account for that kernel
memory? I guess also solvable by charging memlock.
With cgroup based approach we don't need to worry about all that.
From: Andy Lutomirski <luto@amacapital.net> Date: 2016-08-31 03:29:44
On Tue, Aug 30, 2016 at 6:36 PM, Alexei Starovoitov
[off-list ref] wrote:
On Tue, Aug 30, 2016 at 02:45:14PM -0700, Andy Lutomirski wrote:
quoted
One might argue that landlock shouldn't be tied to seccomp (in theory,
attached progs could be given access to syscall_get_xyz()), but I
proposed lsm is way more powerful than syscall_get_xyz.
no need to dumb it down.
I think you're misunderstanding me.
Mickaël's code allows one to make the LSM hook filters depend on the
syscall using SECCOMP_RET_LANDLOCK. I'm suggesting that a similar
effect could be achieved by allowing the eBPF LSM hook to call
syscall_get_xyz() if it wants to.
quoted
think that the seccomp attachment mechanism is the right way to
install unprivileged filters. It handles the no_new_privs stuff, it
allows TSYNC, it's totally independent of systemwide policy, etc.
Trying to use cgroups or similar for this is going to be much nastier.
Some tighter sandboxes (Sandstorm, etc) aren't even going to dream of
putting cgroupfs in their containers, so requiring cgroups or similar
would be a mess for that type of application.
I don't see why it is a 'mess'. cgroups are already used by majority
of the systems, so I don't see why requiring a cgroup is such a big deal.
Requiring cgroup to be configured in isn't a big deal. Requiring
But let's say we don't do them. How implementation is going to look like
for task based hierarchy? Note that we need an array of bpf_prog pointers.
One for each lsm hook. Where this array is going to be stored?
We cannot put in task_struct, since it's too large. Cannot put it
into 'struct seccomp' directly either, unless it will become a pointer.
Is that the proposal?
It would go in struct seccomp_filter or in something pointed to from there.
So now we will be wasting extra 1kbyte of memory per task. Not great.
We'd want to optimize it by sharing this such struct seccomp with prog array
across threads of the same task? Or dynimically allocating it when
landlock is in use? May sound nice, but how to account for that kernel
memory? I guess also solvable by charging memlock.
With cgroup based approach we don't need to worry about all that.
The considerations are essentially identical either way.
With cgroups, if you want to share the memory between multiple
separate sandboxes (Firejail instances, Sandstorm grains, Chromium
instances, xdg-apps, etc), you'd need to get them to all coordinate to
share a cgroup. With a seccomp-like interface, you'd need to get them
to coordinate to share an installed layer (using my FD idea or
similar).
There would *not* be any duplication of this memory just because a
sandboxed process called fork().
--Andy
--
Andy Lutomirski
AMA Capital Management, LLC
From: Pavel Machek <hidden> Date: 2016-09-15 09:19:11
Hi!
This series is a proof of concept to fill some missing part of seccomp as the
ability to check syscall argument pointers or creating more dynamic security
policies. The goal of this new stackable Linux Security Module (LSM) called
Landlock is to allow any process, including unprivileged ones, to create
powerful security sandboxes comparable to the Seatbelt/XNU Sandbox or the
OpenBSD Pledge. This kind of sandbox help to mitigate the security impact of
bugs or unexpected/malicious behaviors in userland applications.
The first RFC [1] was focused on extending seccomp while staying at the syscall
level. This brought a working PoC but with some (mitigated) ToCToU race
conditions due to the seccomp ptrace hole (now fixed) and the non-atomic
syscall argument evaluation (hence the LSM hooks).
Long and nice description follows. Should it go to Documentation/
somewhere?
Because some documentation would be useful...
Pavel
This series is a proof of concept to fill some missing part of seccomp as the
ability to check syscall argument pointers or creating more dynamic security
policies. The goal of this new stackable Linux Security Module (LSM) called
Landlock is to allow any process, including unprivileged ones, to create
powerful security sandboxes comparable to the Seatbelt/XNU Sandbox or the
OpenBSD Pledge. This kind of sandbox help to mitigate the security impact of
bugs or unexpected/malicious behaviors in userland applications.
The first RFC [1] was focused on extending seccomp while staying at the syscall
level. This brought a working PoC but with some (mitigated) ToCToU race
conditions due to the seccomp ptrace hole (now fixed) and the non-atomic
syscall argument evaluation (hence the LSM hooks).
Long and nice description follows. Should it go to Documentation/
somewhere?
Because some documentation would be useful...
Pavel
Right, but I was looking for feedback before investing in documentation. :)
From: Pavel Machek <hidden> Date: 2016-09-24 07:45:32
On Tue 2016-09-20 19:08:23, Mickaël Salaün wrote:
On 15/09/2016 11:19, Pavel Machek wrote:
quoted
Hi!
quoted
This series is a proof of concept to fill some missing part of seccomp as the
ability to check syscall argument pointers or creating more dynamic security
policies. The goal of this new stackable Linux Security Module (LSM) called
Landlock is to allow any process, including unprivileged ones, to create
powerful security sandboxes comparable to the Seatbelt/XNU Sandbox or the
OpenBSD Pledge. This kind of sandbox help to mitigate the security impact of
bugs or unexpected/malicious behaviors in userland applications.
The first RFC [1] was focused on extending seccomp while staying at the syscall
level. This brought a working PoC but with some (mitigated) ToCToU race
conditions due to the seccomp ptrace hole (now fixed) and the non-atomic
syscall argument evaluation (hence the LSM hooks).
Long and nice description follows. Should it go to Documentation/
somewhere?
Because some documentation would be useful...
Right, but I was looking for feedback before investing in documentation. :)
On Tue, Sep 20, 2016 at 10:08 AM, Mickaël Salaün [off-list ref] wrote:
On 15/09/2016 11:19, Pavel Machek wrote:
quoted
Hi!
quoted
This series is a proof of concept to fill some missing part of seccomp as the
ability to check syscall argument pointers or creating more dynamic security
policies. The goal of this new stackable Linux Security Module (LSM) called
Landlock is to allow any process, including unprivileged ones, to create
powerful security sandboxes comparable to the Seatbelt/XNU Sandbox or the
OpenBSD Pledge. This kind of sandbox help to mitigate the security impact of
bugs or unexpected/malicious behaviors in userland applications.
The first RFC [1] was focused on extending seccomp while staying at the syscall
level. This brought a working PoC but with some (mitigated) ToCToU race
conditions due to the seccomp ptrace hole (now fixed) and the non-atomic
syscall argument evaluation (hence the LSM hooks).
Long and nice description follows. Should it go to Documentation/
somewhere?
Because some documentation would be useful...
Pavel
Right, but I was looking for feedback before investing in documentation. :)
Heh, understood. There are a number of grammar issues that slow me
down when reading this, so when it does move into Documentation/, I'll
have some English nit-picks. :)
While reading I found myself wanting an explicit list of "guiding
principles" for anyone implementing new hooks. It is touched on in
several places (don't expose things, don't allow for privilege
changes, etc). Having that spelled out somewhere would be nice.
-Kees
--
Kees Cook
Nexus Security
On Tue, Sep 20, 2016 at 10:08 AM, Mickaël Salaün [off-list ref] wrote:
quoted
On 15/09/2016 11:19, Pavel Machek wrote:
quoted
Hi!
quoted
This series is a proof of concept to fill some missing part of seccomp as the
ability to check syscall argument pointers or creating more dynamic security
policies. The goal of this new stackable Linux Security Module (LSM) called
Landlock is to allow any process, including unprivileged ones, to create
powerful security sandboxes comparable to the Seatbelt/XNU Sandbox or the
OpenBSD Pledge. This kind of sandbox help to mitigate the security impact of
bugs or unexpected/malicious behaviors in userland applications.
The first RFC [1] was focused on extending seccomp while staying at the syscall
level. This brought a working PoC but with some (mitigated) ToCToU race
conditions due to the seccomp ptrace hole (now fixed) and the non-atomic
syscall argument evaluation (hence the LSM hooks).
Long and nice description follows. Should it go to Documentation/
somewhere?
Because some documentation would be useful...
Pavel
Right, but I was looking for feedback before investing in documentation. :)
Heh, understood. There are a number of grammar issues that slow me
down when reading this, so when it does move into Documentation/, I'll
have some English nit-picks. :)
While reading I found myself wanting an explicit list of "guiding
principles" for anyone implementing new hooks. It is touched on in
several places (don't expose things, don't allow for privilege
changes, etc). Having that spelled out somewhere would be nice.
Right, I'm going to try to create a more consistent documentation with
the "guiding principles".
Mickaël