cgroup: syscalls limiting subsystem
Subsystems (4)
RFC v2 1/1 cgroup: syscalls limiting subsystem
Hi,
As I promised, RFC v2 of the syscalls limiting cgroup subsystem is ready. For those who missed previous one here is the shortuct:
http://marc.info/?l=linux-security-module&m=131914567720567&w=2
In v2 I made changes suggested by Paul and Will (thanks!) this means:
1. clearing bits in descendants - it enables less synchronizing so the overhead is smaller (see below)
2. ability to turn on/off (default: off) policy checking at current (and only current!) level of cgroup (see below)
3. some minor fixes
Details:
1. Clearing bits in descendants means that every children is in coherent state with it's parent. This means 4 different kind of concurrent accessing to syscalls_bitmap:
a) Copying bits from parent to children when creating new cgroup.
b) Setting bits in descendants on ALLOW/DENY write.
c) Testing all bits in sequence on printing.
d) Testing one bit in bitmap on policy checking.
c) and d) are testing bits which are guaranteed to be atomic so I decided to not put any critical section in there because it's useless and in d) it's significant overhead. So in c) and d) we get info that is or was (when write occurred) true in some point of time.
Now a) and b) clearly needs syncing. Concurrent writes to the hierarchy could damage the consistency - same with concurrent write and copying. Finally I decided to use one global mutex for write and copying - it saves some memory and code complexity but there is some performance impact that I think is not important because copying and writing to hierarchy is rare.
I think, that I did the syncing right but I am open for any comments on this.
2. I added file (syscalls.checking) which allows to turn on/off policy checking for current level of cgroup. This is not affecting children in any way - all syscalls forbidden in children are still forbidden and all syscalls forbidden in parent are still forbidden in children. I think that making it any other way would complicate the usage to the level of impossibility.
The good thing is that performance for processes within cgroup with checking=0 is basically untouched - there is flag checking and jump added only. So, we can have syscalls subsystem compiled and ready for the use with 0% overhead which was the goal for RFC v2.
The bad thing however, is breaking clarity of this cgroup because we have a flag that is not hierarchical (like everything in cgroup). But I think that is a good subject for a discussion.
Last but not least - the overhead. It is lower in RFC v2 than in RFC v1 and what is even more important - it is the same on all levels in hierarchy (thanks to the change suggested by Paul). Now I am looking for some fair methods of benchmarking it. Previously, I ran many (like 10^8) system calls in a loop and then measure the overhead for it (system time) but it has two drawbacks: first is that different syscalls take different time to complete, second - it doesn't give the real impression on how typical process would be slower - no apps make syscalls only. So, I would love to hear some suggestions how to measure the overhead in a way that provides useful piece of information.
Last thing for the performance is that I found rather simple method of optimizing it even more. Compiler is currently not inlining bitmap checking in syscall path which means jumping and a lot of stack ops. If I was able to write bitmap checking in assembly (or force inlining) the overhead will be smallest possible - only dereferencing pointers and bit testing. I am currently trying to do it but unfortunately I have not succeeded yet. Maybe someone is willing to help with that?
That is all for RFC v2 from me. Please, let me know what you think.
Best Regards,
Lukasz Sowa
arch/x86/include/asm/thread_info.h +2 -0
@@ -95,6 +95,7 @@ struct thread_info {#define TIF_BLOCKSTEP 25/* set when we want DEBUGCTLMSR_BTF */ #define TIF_LAZY_MMU_UPDATES 27/* task is updating the mmu lazily */ #define TIF_SYSCALL_TRACEPOINT 28/* syscall tracepoint instrumentation */ +#define TIF_SYSCALL_USE_CGROUP 29/* is using permission checking in syscalls cgroup */ #define _TIF_SYSCALL_TRACE (1 << TIF_SYSCALL_TRACE)#define _TIF_NOTIFY_RESUME (1 << TIF_NOTIFY_RESUME)
@@ -117,6 +118,7 @@ struct thread_info {#define _TIF_BLOCKSTEP (1 << TIF_BLOCKSTEP)#define _TIF_LAZY_MMU_UPDATES (1 << TIF_LAZY_MMU_UPDATES)#define _TIF_SYSCALL_TRACEPOINT (1 << TIF_SYSCALL_TRACEPOINT) +#define _TIF_SYSCALL_USE_CGROUP (1 << TIF_SYSCALL_USE_CGROUP)/* work to do in syscall_trace_enter() */ #define _TIF_WORK_SYSCALL_ENTRY \
arch/x86/kernel/entry_32.S +28 -0
@@ -425,6 +425,20 @@ sysenter_past_esp: sysenter_do_call: cmpl $(nr_syscalls), %eax jae syscall_badsys +#ifdef CONFIG_CGROUP_SYSCALLS + testl $_TIF_SYSCALL_USE_CGROUP, TI_flags(%ebp) + jz syscalls_cgroup_skip_sysenter + push %eax + push %ecx + push %edx + call syscalls_cgroup_perm + cmpl $0, %eax + pop %edx + pop %ecx + pop %eax + je syscall_badsys +syscalls_cgroup_skip_sysenter: +#endif call *sys_call_table(,%eax,4) movl %eax,PT_EAX(%esp) LOCKDEP_SYS_EXIT
@@ -507,6 +521,20 @@ ENTRY(system_call) cmpl $(nr_syscalls), %eax jae syscall_badsys syscall_call: +#ifdef CONFIG_CGROUP_SYSCALLS + testl $_TIF_SYSCALL_USE_CGROUP, TI_flags(%ebp) + jz syscalls_cgroup_skip_syscall + push %eax + push %ecx + push %edx + call syscalls_cgroup_perm + cmpl $0, %eax + pop %edx + pop %ecx + pop %eax + je syscall_badsys +syscalls_cgroup_skip_syscall: +#endif call *sys_call_table(,%eax,4) movl %eax,PT_EAX(%esp) # store the return value syscall_exit:
arch/x86/kernel/entry_64.S +57 -0
69 lines not on this page · raw message
include/linux/bitops.h +5 -0
@@ -26,6 +26,11 @@ extern unsigned long __sw_hweight64(__u64 w); (bit) < (size); \ (bit) = find_next_bit((addr), (size), (bit) + 1)) +#define for_each_zero_bit (bit, addr, size) \ + for ((bit) = find_first_zero_bit((addr), (size)); \ + (bit) < (size); \ + (bit) = find_next_zero_bit((addr), (size), (bit) + 1)) +static __inline__int get_bitmask_order (unsigned int count) {int order;
include/linux/cgroup_subsys.h +6 -0
@@ -64,3 +64,9 @@ SUBSYS(perf)#endif /* */ + +#ifdef CONFIG_CGROUP_SYSCALLS +SUBSYS(syscalls) +#endif + +/* */
init/Kconfig +7 -0
@@ -609,6 +609,13 @@ config CGROUP_DEVICE Provides a cgroup implementing whitelists for devices which a process in the cgroup can mknod or open. +config CGROUP_SYSCALLS + bool "Syscalls controller for cgroups" + depends on X86 + help + Provides a way to limit access to specified syscalls for + tasks in a cgroup. + config CPUSETS bool "Cpuset support" help
security/Makefile +1 -0
@@ -22,6 +22,7 @@ obj-$(CONFIG_AUDIT) += lsm_audit.o obj-$(CONFIG_SECURITY_TOMOYO) += tomoyo/built-in.o obj-$(CONFIG_SECURITY_APPARMOR) += apparmor/built-in.o obj-$(CONFIG_CGROUP_DEVICE) += device_cgroup.o +obj-$(CONFIG_CGROUP_SYSCALLS) += syscalls_cgroup.o # Object integrity file lists subdir-$(CONFIG_INTEGRITY) += integrity
security/syscalls_cgroup.c new file +273 -0
273 lines not on this page · raw message
A message that is not in the archive (sent off-list or not yet received): [email protected]
Quoted 3 lines
As I promised, RFC v2 of the syscalls limiting cgroup subsystem is ready. For those who missed previous one here is the shortuct:
http://marc.info/?l=linux-security-module&m=131914567720567&w=2
Oops, I did the posting a little too early and pushed wrong version with missing functions for for and attach_task. Correct version below.
One interesting thing that I found out is possible race between syscalls_cgroup_fork() and syscalls_cgroup_switch_write() I don't know how to avoid. If syscalls_cgroup_switch_write() will happen between cgroup_fork_callbacks() and cgroup_post_fork() it will lead to inconsistent setting of TIF in tasks. After some longer thinking the only nice & clean solution I found is to add another callback to cgroup_subsys struct: post_fork. It would be invoked just after the cgroup_post_fork() which will guarantee that task is already on tasks list making it possible to avoid the race. However, this solutions requires to modify the cgroups internals just for my subsystem (at the moment of course) which may not be desired. I would love to hear other solutions/hacks/workarounds.
Thanks,
Lukasz Sowa
arch/x86/include/asm/thread_info.h +2 -0
@@ -95,6 +95,7 @@ struct thread_info {#define TIF_BLOCKSTEP 25/* set when we want DEBUGCTLMSR_BTF */ #define TIF_LAZY_MMU_UPDATES 27/* task is updating the mmu lazily */ #define TIF_SYSCALL_TRACEPOINT 28/* syscall tracepoint instrumentation */ +#define TIF_SYSCALL_USE_CGROUP 29/* is using permission checking in syscalls cgroup */ #define _TIF_SYSCALL_TRACE (1 << TIF_SYSCALL_TRACE)#define _TIF_NOTIFY_RESUME (1 << TIF_NOTIFY_RESUME)
@@ -117,6 +118,7 @@ struct thread_info {#define _TIF_BLOCKSTEP (1 << TIF_BLOCKSTEP)#define _TIF_LAZY_MMU_UPDATES (1 << TIF_LAZY_MMU_UPDATES)#define _TIF_SYSCALL_TRACEPOINT (1 << TIF_SYSCALL_TRACEPOINT) +#define _TIF_SYSCALL_USE_CGROUP (1 << TIF_SYSCALL_USE_CGROUP)/* work to do in syscall_trace_enter() */ #define _TIF_WORK_SYSCALL_ENTRY \
arch/x86/kernel/entry_32.S +28 -0
@@ -425,6 +425,20 @@ sysenter_past_esp: sysenter_do_call: cmpl $(nr_syscalls), %eax jae syscall_badsys +#ifdef CONFIG_CGROUP_SYSCALLS + testl $_TIF_SYSCALL_USE_CGROUP, TI_flags(%ebp) + jz syscalls_cgroup_skip_sysenter + push %eax + push %ecx + push %edx + call syscalls_cgroup_perm + cmpl $0, %eax + pop %edx + pop %ecx + pop %eax + je syscall_badsys +syscalls_cgroup_skip_sysenter: +#endif call *sys_call_table(,%eax,4) movl %eax,PT_EAX(%esp) LOCKDEP_SYS_EXIT
@@ -507,6 +521,20 @@ ENTRY(system_call) cmpl $(nr_syscalls), %eax jae syscall_badsys syscall_call: +#ifdef CONFIG_CGROUP_SYSCALLS + testl $_TIF_SYSCALL_USE_CGROUP, TI_flags(%ebp) + jz syscalls_cgroup_skip_syscall + push %eax + push %ecx + push %edx + call syscalls_cgroup_perm + cmpl $0, %eax + pop %edx + pop %ecx + pop %eax + je syscall_badsys +syscalls_cgroup_skip_syscall: +#endif call *sys_call_table(,%eax,4) movl %eax,PT_EAX(%esp) # store the return value syscall_exit:
arch/x86/kernel/entry_64.S +57 -0
69 lines not on this page · raw message
include/linux/bitops.h +5 -0
@@ -26,6 +26,11 @@ extern unsigned long __sw_hweight64(__u64 w); (bit) < (size); \ (bit) = find_next_bit((addr), (size), (bit) + 1)) +#define for_each_zero_bit (bit, addr, size) \ + for ((bit) = find_first_zero_bit((addr), (size)); \ + (bit) < (size); \ + (bit) = find_next_zero_bit((addr), (size), (bit) + 1)) +static __inline__int get_bitmask_order (unsigned int count) {int order;
include/linux/cgroup_subsys.h +6 -0
@@ -64,3 +64,9 @@ SUBSYS(perf)#endif /* */ + +#ifdef CONFIG_CGROUP_SYSCALLS +SUBSYS(syscalls) +#endif + +/* */
init/Kconfig +7 -0
@@ -609,6 +609,13 @@ config CGROUP_DEVICE Provides a cgroup implementing whitelists for devices which a process in the cgroup can mknod or open. +config CGROUP_SYSCALLS + bool "Syscalls controller for cgroups" + depends on X86 + help + Provides a way to limit access to specified syscalls for + tasks in a cgroup. + config CPUSETS bool "Cpuset support" help
security/Makefile +1 -0
@@ -22,6 +22,7 @@ obj-$(CONFIG_AUDIT) += lsm_audit.o obj-$(CONFIG_SECURITY_TOMOYO) += tomoyo/built-in.o obj-$(CONFIG_SECURITY_APPARMOR) += apparmor/built-in.o obj-$(CONFIG_CGROUP_DEVICE) += device_cgroup.o +obj-$(CONFIG_CGROUP_SYSCALLS) += syscalls_cgroup.o # Object integrity file lists subdir-$(CONFIG_INTEGRITY) += integrity
security/syscalls_cgroup.c new file +309 -0
309 lines not on this page · raw message
Quoted 3 lines
As I promised, RFC v2 of the syscalls limiting cgroup subsystem is ready. For those who missed previous one here is the shortuct:
http://marc.info/?l=linux-security-module&m=131914567720567&w=2
Oops, I did the posting a little too early and pushed wrong version with missing functions for for and attach_task. Correct version below.
One interesting thing that I found out is possible race between syscalls_cgroup_fork() and syscalls_cgroup_switch_write() I don't know how to avoid. If syscalls_cgroup_switch_write() will happen between cgroup_fork_callbacks() and cgroup_post_fork() it will lead to inconsistent setting of TIF in tasks. After some longer thinking the only nice & clean solution I found is to add another callback to cgroup_subsys struct: post_fork. It would be invoked just after the cgroup_post_fork() which will guarantee that task is already on tasks list making it possible to avoid the race. However, this solutions requires to modify the cgroups internals just for my subsystem (at the moment of course) which may not be desired. I would love to hear other solutions/hacks/workarounds.
Thanks,
Lukasz Sowa
arch/x86/include/asm/thread_info.h +2 -0
@@ -95,6 +95,7 @@ struct thread_info {#define TIF_BLOCKSTEP 25/* set when we want DEBUGCTLMSR_BTF */ #define TIF_LAZY_MMU_UPDATES 27/* task is updating the mmu lazily */ #define TIF_SYSCALL_TRACEPOINT 28/* syscall tracepoint instrumentation */ +#define TIF_SYSCALL_USE_CGROUP 29/* is using permission checking in syscalls cgroup */ #define _TIF_SYSCALL_TRACE (1 << TIF_SYSCALL_TRACE)#define _TIF_NOTIFY_RESUME (1 << TIF_NOTIFY_RESUME)
@@ -117,6 +118,7 @@ struct thread_info {#define _TIF_BLOCKSTEP (1 << TIF_BLOCKSTEP)#define _TIF_LAZY_MMU_UPDATES (1 << TIF_LAZY_MMU_UPDATES)#define _TIF_SYSCALL_TRACEPOINT (1 << TIF_SYSCALL_TRACEPOINT) +#define _TIF_SYSCALL_USE_CGROUP (1 << TIF_SYSCALL_USE_CGROUP)/* work to do in syscall_trace_enter() */ #define _TIF_WORK_SYSCALL_ENTRY \
arch/x86/kernel/entry_32.S +28 -0
@@ -425,6 +425,20 @@ sysenter_past_esp: sysenter_do_call: cmpl $(nr_syscalls), %eax jae syscall_badsys +#ifdef CONFIG_CGROUP_SYSCALLS + testl $_TIF_SYSCALL_USE_CGROUP, TI_flags(%ebp) + jz syscalls_cgroup_skip_sysenter + push %eax + push %ecx + push %edx + call syscalls_cgroup_perm + cmpl $0, %eax + pop %edx + pop %ecx + pop %eax + je syscall_badsys +syscalls_cgroup_skip_sysenter: +#endif call *sys_call_table(,%eax,4) movl %eax,PT_EAX(%esp) LOCKDEP_SYS_EXIT
@@ -507,6 +521,20 @@ ENTRY(system_call) cmpl $(nr_syscalls), %eax jae syscall_badsys syscall_call: +#ifdef CONFIG_CGROUP_SYSCALLS + testl $_TIF_SYSCALL_USE_CGROUP, TI_flags(%ebp) + jz syscalls_cgroup_skip_syscall + push %eax + push %ecx + push %edx + call syscalls_cgroup_perm + cmpl $0, %eax + pop %edx + pop %ecx + pop %eax + je syscall_badsys +syscalls_cgroup_skip_syscall: +#endif call *sys_call_table(,%eax,4) movl %eax,PT_EAX(%esp) # store the return value syscall_exit:
arch/x86/kernel/entry_64.S +57 -0
69 lines not on this page · raw message
include/linux/bitops.h +5 -0
@@ -26,6 +26,11 @@ extern unsigned long __sw_hweight64(__u64 w); (bit) < (size); \ (bit) = find_next_bit((addr), (size), (bit) + 1)) +#define for_each_zero_bit (bit, addr, size) \ + for ((bit) = find_first_zero_bit((addr), (size)); \ + (bit) < (size); \ + (bit) = find_next_zero_bit((addr), (size), (bit) + 1)) +static __inline__int get_bitmask_order (unsigned int count) {int order;
include/linux/cgroup_subsys.h +6 -0
@@ -64,3 +64,9 @@ SUBSYS(perf)#endif /* */ + +#ifdef CONFIG_CGROUP_SYSCALLS +SUBSYS(syscalls) +#endif + +/* */
init/Kconfig +7 -0
@@ -609,6 +609,13 @@ config CGROUP_DEVICE Provides a cgroup implementing whitelists for devices which a process in the cgroup can mknod or open. +config CGROUP_SYSCALLS + bool "Syscalls controller for cgroups" + depends on X86 + help + Provides a way to limit access to specified syscalls for + tasks in a cgroup. + config CPUSETS bool "Cpuset support" help
security/Makefile +1 -0
@@ -22,6 +22,7 @@ obj-$(CONFIG_AUDIT) += lsm_audit.o obj-$(CONFIG_SECURITY_TOMOYO) += tomoyo/built-in.o obj-$(CONFIG_SECURITY_APPARMOR) += apparmor/built-in.o obj-$(CONFIG_CGROUP_DEVICE) += device_cgroup.o +obj-$(CONFIG_CGROUP_SYSCALLS) += syscalls_cgroup.o # Object integrity file lists subdir-$(CONFIG_INTEGRITY) += integrity
security/syscalls_cgroup.c new file +309 -0
309 lines not on this page · raw message