├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── ban_CLONE_NEWUSER.c ├── deb ├── .gitignore ├── debian │ ├── changelog │ ├── compat │ ├── control │ ├── files │ ├── install │ ├── rules │ ├── source │ │ └── format │ └── test └── makedeb.sh ├── limit_syscalls.c ├── monitor.sh └── writelimiter ├── .gitignore ├── Makefile ├── README ├── override.c ├── popen2.c ├── popen2.h ├── recv_fd.c ├── recv_fd.h ├── safer.c ├── send_fd.c ├── send_fd.h ├── writelimiter ├── writelimiter.h └── writelimiter_broker.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | limit_syscalls 3 | limit_syscalls_static 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2013 Vitaly Shukela 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: limit_syscalls ban_CLONE_NEWUSER 2 | 3 | limit_syscalls: limit_syscalls.c 4 | ${CC} ${CFLAGS} -Wall limit_syscalls.c -lseccomp -o limit_syscalls 5 | 6 | ban_CLONE_NEWUSER: ban_CLONE_NEWUSER.c 7 | ${CC} ${CFLAGS} -Wall ban_CLONE_NEWUSER.c -lseccomp -o ban_CLONE_NEWUSER 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This program allows user to set up simple [seccomp][1] filter that limits 2 | which syscalls can be called. All the rest syscalls return pre-defined error 3 | value `Channel number out of range`. 4 | 5 | The supplied `monitor.sh` script assists executing of binaries under strace for automatic creating of the allowed syscalls list. 6 | 7 | This program uses [libseccomp][2] library. 8 | The program supports setting comparator for syscall arguments and 9 | specifying alternative default or syscall actions; for simple version see 10 | [nocreep][5] branch. 11 | 12 | Here is pre-built static simple x86 binary: [limit_syscalls_static][3]. 13 | 14 | Note that rules for syscall_limiter are not very versatile. 15 | There are inherent limitations what they can't do 16 | (for example, dereference memory or be stateful). 17 | To address this, you can "ban too much", but also provide external helper that will, 18 | for example, open files or sockets and send them using SCM_RIGHTS to us, implementing 19 | more sophisticated security checks on the way. [Seccomp-nurse][6] uses this approach. 20 | With syscall_limiter you can reduce the amount of what should be helped externally and access helper in more ways. 21 | LD_PRELOAD approach can be used to detour system functions to our helper. This way we will have something in middle between seccomp-nurse and pure-BPF-seccomp-no-helper approaches: some syscalls are just banned, some are just allowed and some are redirected to helper. 22 | 23 | See writelimiter subdirectory for example of such approach. It allows users to start programs with only specified parts of filesystem being writable. 24 | 25 | 26 | You can use `strace -e raw=somesyscall` to discover syscall parameter numbers and values. 27 | 28 | ``` 29 | # strace -e mount -e raw=mount /bin/mount -t tmpfs tmp /tmp/foo -o remount,ro 30 | mount(0x80634c8, 0x80634d8, 0x80634e8, 0xc0ed0021, 0) = 0 31 | 32 | # strace -e mount -e raw=mount /bin/mount -t tmpfs tmp /tmp/foo -o remount,rw 33 | mount(0x80634c8, 0x80634d8, 0x80634e8, 0xc0ed0020, 0) = 0 34 | ``` 35 | 36 | 37 | Examples 38 | === 39 | 40 | Basic 41 | --- 42 | 43 | ``` 44 | $ ./limit_syscalls 45 | Usage: limit_syscalls syscall1 syscall2 ... syscallN -- program [arguments] 46 | Example: 47 | limit_syscalls execve exit write read open close mmap2 fstat64 access mprotect set_thread_area -- /bin/echo qqq 48 | Advanced: 49 | LIMIT_SYSCALLS_DEFAULT_ACTION={a,k,eN} - by default allow, kill or return error N 50 | some_syscall,A0>=3,A4<<1000,A1!=4,A2&&0x0F==0x0F - compare arguments 51 | some_syscall,{a,k,eN} - allow, kill or return error N for this rule 52 | Some more example: 53 | LIMIT_SYSCALLS_DEFAULT_ACTION=a limit_syscalls 'write,A0==1,e0' -- /usr/bin/printf --help 54 | (this makes write to stdout in /usr/bin/printf silently fail, looping it) 55 | Restrict user namespace (CLONE_NEWUSER): 56 | LIMIT_SYSCALLS_DEFAULT_ACTION=a limit_syscalls clone,A0\&\&0x10000000==0x10000000,e1 unshare,e1 -- /bin/bash 57 | for this use case there is a separate executable ban_CLONE_NEWUSER 58 | 59 | 60 | $ ./limit_syscalls execve exit write read open close mmap2 fstat64 access mprotect set_thread_area -- /bin/echo qqq 61 | qqq 62 | 63 | $ # Let's cut off "exit". 64 | $ ./limit_syscalls execve write read open close mmap2 fstat64 access mprotect set_thread_area -- /bin/echo qqq 65 | qqq 66 | Segmentation fault 67 | 68 | ``` 69 | 70 | ping 71 | --- 72 | 73 | ``` 74 | # ./monitor.sh ping 127.0.0.1 75 | PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data. 76 | 64 bytes from 127.0.0.1: icmp_req=1 ttl=64 time=0.132 ms 77 | 64 bytes from 127.0.0.1: icmp_req=2 ttl=64 time=0.108 ms 78 | ^C 79 | --- 127.0.0.1 ping statistics --- 80 | 81 | # limit_syscalls access brk close connect execve exit_group fstat64 getpid getsockname getsockopt gettimeofday getuid32 ioctl mmap2 mprotect munmap open poll read recvmsg rt_sigaction sendmsg setsockopt set_thread_area setuid32 sigreturn socket write -- /bin/ping 82 | 83 | # ./limit_syscalls access brk close connect execve exit_group fstat64 getpid getsockname getsockopt gettimeofday getuid32 ioctl mmap2 mprotect munmap open poll read recvmsg rt_sigaction sendmsg setsockopt set_thread_area setuid32 sigreturn socket write -- /bin/ping 127.0.0.1 84 | PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data. 85 | 64 bytes from 127.0.0.1: icmp_req=1 ttl=64 time=0.132 ms 86 | 64 bytes from 127.0.0.1: icmp_req=2 ttl=64 time=0.108 ms 87 | ^C 88 | --- 127.0.0.1 ping statistics --- 89 | 2 packets transmitted, 2 received, 0% packet loss, time 999ms 90 | rtt min/avg/max/mdev = 0.108/0.120/0.132/0.012 ms 91 | 92 | # 93 | ``` 94 | 95 | firefox 96 | --- 97 | 98 | ``` 99 | $ ./monitor.sh /opt/firefox-17esr/firefox 100 | $ 101 | $ limit_syscalls access bind brk chmod clock_getres clock_gettime clone close connect dup2 epoll_create epoll_ctl epoll_wait eventfd2 execve exit exit_group fallocate fcntl64 fstat64 fstatfs fsync ftruncate64 futex getcwd getdents getdents64 getegid32 geteuid32 getgid32 getpeername getresgid32 getresuid32 getrlimit getrusage getsockname getsockopt gettid gettimeofday getuid32 ioctl _llseek lseek lstat64 madvise mkdir mmap2 mprotect munmap open pipe poll prctl pwrite64 read readahead readlink recv recvfrom recvmsg rename rmdir rt_sigaction rt_sigprocmask sched_getaffinity sched_getparam sched_get_priority_max sched_get_priority_min sched_getscheduler sched_setscheduler send sendmsg sendto set_robust_list setsockopt set_thread_area set_tid_address shmat shmctl shmdt shmget shutdown sigaltstack socket socketpair stat64 statfs statfs64 symlink sysinfo time umask uname unlink utime waitpid write writev -- /opt/firefox-17esr/firefox 102 | 103 | $ ./limit_syscalls access bind brk chmod clock_getres clock_gettime clone close connect dup2 epoll_create epoll_ctl epoll_wait eventfd2 execve exit exit_group fallocate fcntl64 fstat64 fstatfs fsync ftruncate64 futex getcwd getdents getdents64 getegid32 geteuid32 getgid32 getpeername getresgid32 getresuid32 getrlimit getrusage getsockname getsockopt gettid gettimeofday getuid32 ioctl _llseek lseek lstat64 madvise mkdir mmap2 mprotect munmap open pipe poll prctl pwrite64 read readahead readlink recv recvfrom recvmsg rename rmdir rt_sigaction rt_sigprocmask sched_getaffinity sched_getparam sched_get_priority_max sched_get_priority_min sched_getscheduler sched_setscheduler send sendmsg sendto set_robust_list setsockopt set_thread_area set_tid_address shmat shmctl shmdt shmget shutdown sigaltstack socket socketpair stat64 statfs statfs64 symlink sysinfo time umask uname unlink utime waitpid write writev -- /opt/firefox-17esr/firefox 104 | 105 | (firefox:10950): Gdk-WARNING **: shmget failed: error 44 (Channel number out of range) 106 | 107 | $ # firefox seems to be working although 108 | 109 | ``` 110 | 111 | [1]:http://en.wikipedia.org/wiki/Seccomp 112 | [2]:http://sourceforge.net/projects/libseccomp/ 113 | [3]:http://vi-server.org/pub/limit_syscalls_static 114 | [5]:https://github.com/vi/syscall_limiter/tree/nocreep 115 | [6]:http://chdir.org/~nico/seccomp-nurse/ 116 | -------------------------------------------------------------------------------- /ban_CLONE_NEWUSER.c: -------------------------------------------------------------------------------- 1 | #include // for CLONE_NEWUSER 2 | #include // for seccomp_rule_add, etc 3 | #include // for uint32_t 4 | #include // for fprintf, perror, stderr 5 | #include // for execve 6 | 7 | // gcc ban_CLONE_NEWUSER.c -lseccomp -o ban_CLONE_NEWUSER 8 | 9 | int main(int argc, char* argv[], char* envp[]) { 10 | 11 | if (argc<2) { 12 | fprintf(stderr, "Usage: ban_CLONE_NEWUSER program [arguments]\n"); 13 | fprintf(stderr, "Bans unshare(2) with any flags and clone(2) with CLONE_NEWUSER flag\n"); 14 | return 126; 15 | } 16 | 17 | uint32_t default_action = SCMP_ACT_ALLOW; 18 | 19 | scmp_filter_ctx ctx = seccomp_init(default_action); 20 | if (!ctx) { 21 | perror("seccomp_init"); 22 | return 126; 23 | } 24 | 25 | int ret = 0; 26 | ret |= seccomp_rule_add(ctx, SCMP_ACT_ERRNO(1), seccomp_syscall_resolve_name("unshare"), 0); 27 | ret |= seccomp_rule_add(ctx, SCMP_ACT_ERRNO(1), seccomp_syscall_resolve_name("clone"), 1, SCMP_CMP(0, SCMP_CMP_MASKED_EQ, CLONE_NEWUSER, CLONE_NEWUSER)); 28 | 29 | if (ret!=0) { 30 | fprintf(stderr, "seccomp_rule_add returned %d\n", ret); 31 | return 124; 32 | } 33 | 34 | ret = seccomp_load(ctx); 35 | if (ret!=0) { 36 | fprintf(stderr, "seccomp_load returned %d\n", ret); 37 | return 124; 38 | } 39 | 40 | execve(argv[1], argv+1, envp); 41 | 42 | perror("execve"); 43 | return 127; 44 | } 45 | -------------------------------------------------------------------------------- /deb/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | -------------------------------------------------------------------------------- /deb/debian/changelog: -------------------------------------------------------------------------------- 1 | syscalllimiter (0.1-1) unstable; urgency=low 2 | 3 | * Initial release (Closes: #...) 4 | 5 | -- Vitaly _Vi Shukela Fri, 18 Jul 2014 02:13:03 +0300 6 | -------------------------------------------------------------------------------- /deb/debian/compat: -------------------------------------------------------------------------------- 1 | 8 2 | -------------------------------------------------------------------------------- /deb/debian/control: -------------------------------------------------------------------------------- 1 | Source: syscalllimiter 2 | Section: utils 3 | Priority: extra 4 | Maintainer: Vitaly _Vi Shukela 5 | Standards-Version: 3.9.3 6 | Homepage: https://github.com/vi/syscall_limiter.git 7 | 8 | Package: syscalllimiter 9 | Architecture: any 10 | Depends: ${shlibs:Depends}, ${misc:Depends}, libc6 11 | Description: This program allows user to set up simple [seccomp][1] filter that limits which syscalls can be called. 12 | -------------------------------------------------------------------------------- /deb/debian/files: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /deb/debian/install: -------------------------------------------------------------------------------- 1 | limit_syscalls /usr/bin/ 2 | -------------------------------------------------------------------------------- /deb/debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | %: 4 | dh $@ 5 | 6 | override_dh_auto_test: 7 | true 8 | -------------------------------------------------------------------------------- /deb/debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /deb/debian/test: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /deb/makedeb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | P=syscalllimiter 5 | V=0.1 6 | FILES=".gitignore LICENSE Makefile README.md limit_syscalls.c monitor.sh writelimiter " 7 | 8 | rm -Rf "$P"-$V 9 | trap "rm -fR \"$P\"-$V" EXIT 10 | mkdir "$P"-$V 11 | for i in $FILES; do cp -Rv ../"$i" "$P"-$V/; done 12 | tar -czf ${P}_$V.orig.tar.gz "$P"-$V 13 | 14 | cp -R debian "$P"-$V 15 | (cd "$P"-$V && debuild) 16 | -------------------------------------------------------------------------------- /limit_syscalls.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | // gcc limit_syscalls.c -lseccomp -o limit_syscalls 9 | 10 | // https://github.com/vi/syscall_limiter 11 | 12 | // Created by Vitaly "_Vi" Shukela; 2013; License=MIT 13 | 14 | 15 | /* Workaround missing va_list version of seccomp_rule_add */ 16 | static int seccomp_rule_add_hack(scmp_filter_ctx ctx, uint32_t action, 17 | int syscall, unsigned int arg_cnt, struct scmp_arg_cmp *args) { 18 | if(arg_cnt==0) return seccomp_rule_add(ctx, action, syscall, arg_cnt); 19 | if(arg_cnt==1) return seccomp_rule_add(ctx, action, syscall, arg_cnt, 20 | args[0]); 21 | if(arg_cnt==2) return seccomp_rule_add(ctx, action, syscall, arg_cnt, 22 | args[0], args[1]); 23 | if(arg_cnt==3) return seccomp_rule_add(ctx, action, syscall, arg_cnt, 24 | args[0], args[1], args[2]); 25 | if(arg_cnt==4) return seccomp_rule_add(ctx, action, syscall, arg_cnt, 26 | args[0], args[1], args[2], args[3]); 27 | if(arg_cnt==5) return seccomp_rule_add(ctx, action, syscall, arg_cnt, 28 | args[0], args[1], args[2], args[3], args[4]); 29 | if(arg_cnt==6) return seccomp_rule_add(ctx, action, syscall, arg_cnt, 30 | args[0], args[1], args[2], args[3], args[4], args[5]); 31 | return -1; 32 | } 33 | 34 | int main(int argc, char* argv[], char* envp[]) { 35 | 36 | if (argc<3 || !strcmp(argv[1], "-?") || !strcmp(argv[1], "--help") || !strcmp(argv[1], "--version")) { 37 | fprintf(stderr, "Usage: limit_syscalls syscall1 syscall2 ... syscallN -- program [arguments]\n"); 38 | fprintf(stderr, "Example:\n" 39 | " limit_syscalls execve exit write read open close mmap2 fstat64 access mprotect set_thread_area -- /bin/echo qqq\n"); 40 | fprintf(stderr, "Advanced:\n"); 41 | fprintf(stderr, " LIMIT_SYSCALLS_DEFAULT_ACTION={a,k,eN} - by default allow, kill or return error N\n"); 42 | fprintf(stderr, " some_syscall,A0>=3,A4<<1000,A1!=4,A2&&0x0F==0x0F - compare arguments\n"); 43 | fprintf(stderr, " some_syscall,{a,k,eN} - allow, kill or return error N for this rule\n"); 44 | fprintf(stderr, "Some more example:\n"); 45 | fprintf(stderr," LIMIT_SYSCALLS_DEFAULT_ACTION=a limit_syscalls 'write,A0==1,e0' -- /usr/bin/printf --help\n"); 46 | fprintf(stderr," (this makes write to stdout in /usr/bin/printf silently fail, looping it)\n"); 47 | fprintf(stderr, "Restrict user namespace:\n"); 48 | fprintf(stderr, " LIMIT_SYSCALLS_DEFAULT_ACTION=a limit_syscalls clone,A0\\&\\&0x10000000==0x10000000,e1 unshare,e1 -- /bin/bash\n"); 49 | return 126; 50 | } 51 | 52 | // ECHRNG, just to provide more or less noticable message when we block a syscall 53 | uint32_t default_action = SCMP_ACT_ERRNO(44); 54 | 55 | if (getenv("LIMIT_SYSCALLS_DEFAULT_ACTION")) { 56 | const char* e = getenv("LIMIT_SYSCALLS_DEFAULT_ACTION"); 57 | if(!strcmp(e, "a")) default_action=SCMP_ACT_ALLOW; 58 | else 59 | if(!strcmp(e, "k")) default_action=SCMP_ACT_KILL; 60 | else 61 | if(e[0] == 'e') { 62 | int errno_; 63 | if (sscanf(e+1,"%i", &errno_)!=1) { 64 | fprintf(stderr, "LIMIT_SYSCALLS_DEFAULT_ACTION=e expected\n"); 65 | return 109; 66 | } 67 | default_action=SCMP_ACT_ERRNO(errno_); 68 | } 69 | else 70 | { 71 | fprintf(stderr, "LIMIT_SYSCALLS_DEFAULT_ACTION should be a, k or e\n"); 72 | return 110; 73 | } 74 | } 75 | 76 | scmp_filter_ctx ctx = seccomp_init(default_action); 77 | if (!ctx) { 78 | perror("seccomp_init"); 79 | return 126; 80 | } 81 | 82 | int i; 83 | 84 | for (i=1; i='0' && aa[1]<='5') ) { 109 | fprintf(stderr, "A[0-5] expected in %s\n", argv[i]); 110 | return 100; 111 | } 112 | int cmp = 0; /* invalid value */ 113 | 114 | if(!strncmp(aa+2, "!=", 2)) cmp=SCMP_CMP_NE; 115 | if(!strncmp(aa+2, "<<", 2)) cmp=SCMP_CMP_LT; 116 | if(!strncmp(aa+2, "<=", 2)) cmp=SCMP_CMP_LE; 117 | if(!strncmp(aa+2, "==", 2)) cmp=SCMP_CMP_EQ; 118 | if(!strncmp(aa+2, ">=", 2)) cmp=SCMP_CMP_GE; 119 | if(!strncmp(aa+2, ">>", 2)) cmp=SCMP_CMP_GT; 120 | if(!strncmp(aa+2, "&&", 2)) cmp=SCMP_CMP_MASKED_EQ; 121 | 122 | if (!cmp) { 123 | fprintf(stderr, "After An there should be comparison operator like" 124 | " != << <= == => >> ot &&; in %s\n", argv[i]); 125 | return 101; 126 | } 127 | 128 | if (cmp != SCMP_CMP_MASKED_EQ) { 129 | scmp_datum_t datum; 130 | if(sscanf(aa+4, "%lli", &datum)!=1) { 131 | fprintf(stderr, "After AxOP there should be some sort of number in %s\n", argv[i]); 132 | return 102; 133 | } 134 | 135 | args[nargs++] = SCMP_CMP(aa[1]-'0', cmp, datum); 136 | } else { 137 | scmp_datum_t mask; 138 | scmp_datum_t datum; 139 | if(sscanf(aa+4, "%lli==%lli", &mask, &datum)!=2) { 140 | fprintf(stderr, "After Ax&& there should be number==number; in %s\n", argv[i]); 141 | return 104; 142 | } 143 | 144 | args[nargs++] = SCMP_CMP(aa[1]-'0', SCMP_CMP_MASKED_EQ, mask, datum); 145 | } 146 | } else 147 | if (aa[0]=='e') { 148 | int errno_; 149 | if (sscanf(aa+1,"%i", &errno_)!=1) { 150 | fprintf(stderr, "After e should be number in %s\n", argv[i]); 151 | return 105; 152 | } 153 | 154 | action = SCMP_ACT_ERRNO(errno_); 155 | } else 156 | if (aa[0]=='k') { 157 | action = SCMP_ACT_KILL; 158 | } else 159 | if (aa[0]=='a') { 160 | action = SCMP_ACT_ALLOW; 161 | } else { 162 | fprintf(stderr, "Unknown %c in %s\n", aa[0], argv[i]); 163 | return 107; 164 | } 165 | } 166 | 167 | int ret = seccomp_rule_add_hack(ctx, action, syscall, nargs, args); 168 | 169 | if (ret!=0) { 170 | fprintf(stderr, "seccomp_rule_add returned %d\n", ret); 171 | return 124; 172 | } 173 | } 174 | 175 | int ret = seccomp_load(ctx); 176 | if (ret!=0) { 177 | fprintf(stderr, "seccomp_load returned %d\n", ret); 178 | } 179 | 180 | execve(argv[i+1], argv+i+1, envp); 181 | 182 | perror("execve"); 183 | return 123; 184 | } 185 | -------------------------------------------------------------------------------- /monitor.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Created by Vitaly "_Vi" Shukela; 2013; License=MIT 4 | 5 | if [ -z "$1" ]; then 6 | echo "Usage: ./monitor.sh program [arguments]" 7 | echo "It should output the limit_syscalls command pointing with all occured syscall enabled" 8 | exit 1 9 | fi 10 | 11 | strace -f -o >( bash -c "(perl -ne '/^\d+\s+([a-z_0-9]+)\(/ and print \"\$1\n\"' | sort | uniq | sed 's/^_exit\$/exit/' | tr '\n' ' ' | sed 's/^/limit_syscalls /; s/\$/ -- /'; which \"$1\" ; echo)>&2"; ) -- "$@" 12 | -------------------------------------------------------------------------------- /writelimiter/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | writelimiter_broker 3 | libwritelimiter.so 4 | -------------------------------------------------------------------------------- /writelimiter/Makefile: -------------------------------------------------------------------------------- 1 | all: libwritelimiter.so writelimiter_broker 2 | 3 | libwritelimiter.so: override.c send_fd.c recv_fd.c writelimiter.h 4 | ${CC} ${CFLAGS} -Wall -g3 override.c send_fd.c recv_fd.c -shared -fPIC -ldl -o libwritelimiter.so 5 | 6 | writelimiter_broker: writelimiter_broker.c send_fd.c recv_fd.c writelimiter.h popen2.h popen2.c safer.c 7 | ${CC} ${CFLAGS} -Wall -g3 writelimiter_broker.c send_fd.c recv_fd.c safer.c popen2.c -o writelimiter_broker 8 | 9 | -------------------------------------------------------------------------------- /writelimiter/README: -------------------------------------------------------------------------------- 1 | Prototype of writelimiter: 2 | syscall_limiter forbids using open with writing flag (and other FS-modifying calls), 3 | libwritelimiter.so redirects failed opens to writelimiter_broker 4 | writelimiter_broker consults with policy_prog and does the actual open and sends FD into sandbox 5 | 6 | Current limitations: 7 | 1. Security is not thought thought yet; 8 | 2. Uses hardcoded FD numbers: 33 and 34; 9 | 3. UNIX sockets connections are not monitored; 10 | read, stat, getfdents, etc. access is just granted everywhere; 11 | (we need it to allow dynamically linked binaries to work) 12 | execve is allowed everywhere; 13 | 4. Static binaries just won't be able to write to FS; 14 | 5. Tested mostly on my system. 15 | 16 | Example: 17 | $ POLICY="perl -ne ' 18 | INIT{$|=1;} 19 | print 0 and next if /\.\./; 20 | print 1 and next if m@^/tmp/@; 21 | print 1 and next if m@^/dev/null\$@; 22 | print 0 '" 23 | $ ./writelimiter "$POLICY" /bin/bash -c 'echo qqq > www' 24 | /bin/bash: www: Permission denied 25 | $ ./writelimiter "$POLICY" /bin/bash -c 'echo qqq > /tmp/www' 26 | $ ./writelimiter "$POLICY" /bin/bash -c 'echo qqq > /tmp/../tmp/www' 27 | /bin/bash: /tmp/../tmp/www: Permission denied 28 | $ ./writelimiter "$POLICY" /bin/bash -c 'echo qqq > /dev/null' 29 | $ ./writelimiter "$POLICY" /bin/bash -c 'echo qqq > /dev/zero' 30 | /bin/bash: /dev/zero: Permission denied 31 | -------------------------------------------------------------------------------- /writelimiter/override.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 1 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | 12 | // Created by Vitaly "_Vi" Shukela; 2013; License=MIT 13 | 14 | /* I want only O_CREAT|O_WRONLY|O_TRUNC, not queer open's or creat's signatures */ 15 | //#include 16 | #define _FCNTL_H 17 | #include 18 | 19 | 20 | #include "writelimiter.h" 21 | 22 | #include "recv_fd.h" 23 | #include "send_fd.h" 24 | 25 | #define SOCKET 33 26 | #define TICKET_SOCKET 34 27 | 28 | static int absolutize_path(struct request *r, const char* pathname, int dirfd) { 29 | if(pathname[0]!='/') { 30 | int l; 31 | // relative path 32 | if (dirfd == AT_FDCWD) { 33 | char* ret = getcwd(r->pathname, sizeof(r->pathname)); 34 | if (!ret) { 35 | return -1; 36 | } 37 | l = strlen(r->pathname); 38 | if (l==PATH_MAX) { 39 | errno=ERANGE; 40 | return -1; 41 | } 42 | } else { 43 | char proce[128]; 44 | sprintf(proce, "/proc/self/fd/%d", dirfd); 45 | 46 | ssize_t ret = readlink(proce, r->pathname, sizeof(r->pathname)); 47 | 48 | if( ret==-1) { 49 | /* Let's just assume dirfd is for "/" (as in /bin/rm) */ 50 | fprintf(stderr, "Warning: can't readlink %s, continuing\n", proce); 51 | l=0; 52 | } else { 53 | l=ret; 54 | } 55 | 56 | } 57 | r->pathname[l]='/'; 58 | strncpy(r->pathname+l+1, pathname, PATH_MAX-l-1); 59 | } else { 60 | // absolute path 61 | strncpy(r->pathname, pathname, PATH_MAX); 62 | } 63 | return 0; 64 | } 65 | 66 | static int receive_ticket() { 67 | char buf[6]; 68 | return read(TICKET_SOCKET, buf, 6); 69 | } 70 | 71 | static int perform_request(const struct request* r) { 72 | write(SOCKET, r, sizeof(*r)); 73 | int ret = read(SOCKET, &errno, sizeof(errno)); 74 | if(ret==-1)return -1; 75 | if(errno) { 76 | return -1; 77 | } else { 78 | int ret = 0; 79 | if (r->operation=='o') { 80 | ret = recv_fd(SOCKET); 81 | } 82 | return ret; 83 | } 84 | } 85 | 86 | 87 | static int remote_openat(int dirfd, const char *pathname, int flags, mode_t mode) { 88 | receive_ticket(); 89 | 90 | struct request r; 91 | r.operation = 'o'; 92 | r.flags = flags; 93 | r.mode = mode; 94 | 95 | if (absolutize_path(&r, pathname, dirfd) == -1) return -1; 96 | 97 | return perform_request(&r); 98 | } 99 | 100 | static int remote_open(const char *pathname, int flags, mode_t mode) { 101 | return remote_openat(AT_FDCWD, pathname, flags, mode); 102 | } 103 | 104 | static int remote_open64(const char *pathname, int flags, mode_t mode) { 105 | return remote_open(pathname, flags, mode); } 106 | static int remote_openat64(int dirfd, const char *pathname, int flags, mode_t mode) { 107 | return remote_openat(dirfd, pathname, flags, mode); } 108 | static int remote_creat(const char *pathname, mode_t mode) { 109 | return remote_open(pathname, O_CREAT|O_WRONLY|O_TRUNC, mode); } 110 | static int remote_creat64(const char *pathname, mode_t mode) { 111 | return remote_open(pathname, O_CREAT|O_WRONLY|O_TRUNC, mode); } 112 | 113 | static int remote_mkdir(const char *pathname, mode_t mode) { 114 | receive_ticket(); 115 | 116 | struct request r; 117 | r.operation = 'm'; 118 | r.mode = mode; 119 | 120 | if (absolutize_path(&r, pathname, AT_FDCWD) == -1) return -1; 121 | 122 | return perform_request(&r); 123 | } 124 | 125 | static int remote_chmod(const char *pathname, mode_t mode) { 126 | receive_ticket(); 127 | 128 | struct request r; 129 | r.operation = 'c'; 130 | r.mode = mode; 131 | 132 | if (absolutize_path(&r, pathname, AT_FDCWD) == -1) return -1; 133 | 134 | return perform_request(&r); 135 | } 136 | 137 | static int remote_rmdir(const char *pathname) { 138 | receive_ticket(); 139 | 140 | struct request r; 141 | r.operation = 'r'; 142 | 143 | if (absolutize_path(&r, pathname, AT_FDCWD) == -1) return -1; 144 | 145 | return perform_request(&r); 146 | } 147 | 148 | static int remote_unlink (const char *pathname) { 149 | receive_ticket(); 150 | 151 | struct request r; 152 | r.operation = 'u'; 153 | 154 | if (absolutize_path(&r, pathname, AT_FDCWD) == -1) return -1; 155 | 156 | return perform_request(&r); 157 | } 158 | 159 | 160 | static int remote_unlinkat (int dirfd, const char *pathname, int flags) { 161 | receive_ticket(); 162 | 163 | struct request r; 164 | r.operation = 'u'; 165 | 166 | if (absolutize_path(&r, pathname, dirfd) == -1) return -1; 167 | 168 | int ret = perform_request(&r); 169 | if (ret == -1 && (flags & AT_REMOVEDIR)) { 170 | r.operation='r'; 171 | ret = perform_request(&r); 172 | } 173 | return ret; 174 | } 175 | 176 | static int remote_renameat(int olddirfd, const char *oldpath, 177 | int newdirfd, const char *newpath) { 178 | receive_ticket(); 179 | 180 | struct request r; 181 | r.operation = 'A'; 182 | 183 | if (absolutize_path(&r, oldpath, olddirfd) == -1) return -1; 184 | 185 | int ret = perform_request(&r); 186 | 187 | if (ret==-1) return -1; 188 | 189 | r.operation = 'n'; 190 | 191 | if (absolutize_path(&r, newpath, newdirfd) == -1) { 192 | perform_request(&r); 193 | return -1; 194 | } 195 | 196 | return perform_request(&r); 197 | } 198 | 199 | static int remote_rename (const char *oldpath, const char *newpath) { 200 | return remote_renameat(AT_FDCWD, oldpath, AT_FDCWD, newpath); 201 | } 202 | 203 | 204 | static int remote_symlinkat(const char *oldpath, int newdirfd, const char *newpath) { 205 | receive_ticket(); 206 | 207 | struct request r; 208 | r.operation = 'A'; 209 | 210 | if (absolutize_path(&r, oldpath, AT_FDCWD) == -1) return -1; 211 | 212 | int ret = perform_request(&r); 213 | 214 | if (ret==-1) return -1; 215 | 216 | r.operation = 's'; 217 | 218 | if (absolutize_path(&r, newpath, newdirfd) == -1) { 219 | perform_request(&r); 220 | return -1; 221 | } 222 | 223 | return perform_request(&r); 224 | } 225 | 226 | static int remote_symlink(const char *oldpath, const char *newpath) { 227 | return remote_symlinkat(oldpath, AT_FDCWD, newpath); 228 | } 229 | 230 | 231 | static int remote_linkat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath, int flags) { 232 | receive_ticket(); 233 | 234 | struct request r; 235 | r.operation = 'A'; 236 | 237 | if (absolutize_path(&r, oldpath, olddirfd) == -1) return -1; 238 | 239 | int ret = perform_request(&r); 240 | 241 | if (ret==-1) return -1; 242 | 243 | r.operation = 'l'; 244 | 245 | if (absolutize_path(&r, newpath, newdirfd) == -1) { 246 | perform_request(&r); 247 | return -1; 248 | } 249 | 250 | r.flags = flags; 251 | return perform_request(&r); 252 | } 253 | 254 | static int remote_link(const char *oldpath, const char *newpath) { 255 | return remote_linkat(AT_FDCWD, oldpath, AT_FDCWD, newpath, 0); 256 | } 257 | 258 | static int remote_mknod(const char *pathname, mode_t mode, dev_t dev) { 259 | receive_ticket(); 260 | 261 | struct request r; 262 | r.operation = 'k'; 263 | r.mode = mode; 264 | r.dev = dev; 265 | 266 | if (absolutize_path(&r, pathname, AT_FDCWD) == -1) return -1; 267 | 268 | return perform_request(&r); 269 | } 270 | 271 | static int remote_mkfifo(const char *pathname, mode_t mode) { 272 | return remote_mknod(pathname, mode|S_IFIFO, 0); 273 | } 274 | 275 | /* Taken from musl-0.9.7 */ 276 | static int __fmodeflags(const char *mode) 277 | { 278 | int flags; 279 | if (strchr(mode, '+')) flags = O_RDWR; 280 | else if (*mode == 'r') flags = O_RDONLY; 281 | else flags = O_WRONLY; 282 | if (strchr(mode, 'x')) flags |= O_EXCL; 283 | if (strchr(mode, 'e')) flags |= O_CLOEXEC; 284 | if (*mode != 'r') flags |= O_CREAT; 285 | if (*mode == 'w') flags |= O_TRUNC; 286 | if (*mode == 'a') flags |= O_APPEND; 287 | return flags; 288 | } 289 | 290 | 291 | 292 | static FILE* remote_fopen(const char *path, const char *mode) { 293 | int flags = __fmodeflags(mode); 294 | int ret = remote_open(path, flags|O_LARGEFILE, 0666); 295 | if (ret==-1) return NULL; 296 | return fdopen(ret, mode); 297 | } 298 | 299 | static FILE* remote_fopen64(const char *path, const char *mode) { 300 | return remote_fopen(path, mode); 301 | } 302 | 303 | 304 | #define OVERIDE_TEMPLATE_I(name, rettype, succcheck, signature, sigargs) \ 305 | static rettype (*orig_##name) signature = NULL; \ 306 | rettype name signature { \ 307 | if(!orig_##name) { \ 308 | orig_##name = dlsym(RTLD_NEXT, #name); \ 309 | } \ 310 | \ 311 | rettype ret = (*orig_##name) sigargs; \ 312 | if (succcheck) return ret; \ 313 | return remote_##name sigargs; \ 314 | } 315 | 316 | #define OVERIDE_TEMPLATE(name, signature, sigargs) \ 317 | OVERIDE_TEMPLATE_I(name, int, ret!=-1, signature, sigargs) 318 | 319 | 320 | OVERIDE_TEMPLATE(open, (const char *pathname, int flags, mode_t mode), (pathname, flags, mode)) 321 | OVERIDE_TEMPLATE(open64, (const char *pathname, int flags, mode_t mode), (pathname, flags, mode)) 322 | OVERIDE_TEMPLATE(openat, (int dirfd, const char *pathname, int flags, mode_t mode), (dirfd, pathname, flags, mode)) 323 | OVERIDE_TEMPLATE(openat64, (int dirfd, const char *pathname, int flags, mode_t mode), (dirfd, pathname, flags, mode)) 324 | OVERIDE_TEMPLATE(creat, (const char *pathname, mode_t mode), (pathname, mode)) 325 | OVERIDE_TEMPLATE(creat64, (const char *pathname, mode_t mode), (pathname, mode)) 326 | OVERIDE_TEMPLATE(mkdir, (const char *pathname, mode_t mode), (pathname, mode)) 327 | OVERIDE_TEMPLATE(mknod, (const char *pathname, mode_t mode, dev_t dev), (pathname, mode, dev)) 328 | OVERIDE_TEMPLATE(mkfifo, (const char *pathname, mode_t mode), (pathname, mode)) 329 | OVERIDE_TEMPLATE(chmod, (const char *pathname, mode_t mode), (pathname, mode)) 330 | OVERIDE_TEMPLATE(rmdir, (const char *pathname), (pathname)) 331 | OVERIDE_TEMPLATE(unlink, (const char *pathname), (pathname)) 332 | OVERIDE_TEMPLATE(unlinkat, (int dirfd, const char *pathname, int flags), (dirfd, pathname, flags)) 333 | OVERIDE_TEMPLATE(rename, (const char *oldpath, const char *newpath), (oldpath, newpath)) 334 | OVERIDE_TEMPLATE(renameat, (int olddirfd, const char *oldpath, int newdirfd, const char *newpath), (olddirfd, oldpath, newdirfd, newpath)) 335 | OVERIDE_TEMPLATE(link, (const char *oldpath, const char *newpath), (oldpath, newpath)) 336 | OVERIDE_TEMPLATE(linkat, (int olddirfd, const char *oldpath, int newdirfd, const char *newpath, int flags), (olddirfd, oldpath, newdirfd, newpath, flags)) 337 | OVERIDE_TEMPLATE(symlink, (const char *oldpath, const char *newpath), (oldpath, newpath)) 338 | OVERIDE_TEMPLATE(symlinkat, (const char *oldpath, int newdirfd, const char *newpath), (oldpath, newdirfd, newpath)) 339 | 340 | 341 | OVERIDE_TEMPLATE_I(fopen, FILE*, ret != NULL, (const char *path, const char *mode), (path, mode)) 342 | OVERIDE_TEMPLATE_I(fopen64, FILE*, ret != NULL, (const char *path, const char *mode), (path, mode)) 343 | -------------------------------------------------------------------------------- /writelimiter/popen2.c: -------------------------------------------------------------------------------- 1 | // http://media.unpythonic.net/emergent-files/01108826729/popen2.c 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "popen2.h" 10 | 11 | int popen2(const char *cmdline, struct popen2 *childinfo) { 12 | pid_t p; 13 | int pipe_stdin[2], pipe_stdout[2]; 14 | 15 | if(pipe(pipe_stdin)) return -1; 16 | if(pipe(pipe_stdout)) return -1; 17 | 18 | //printf("pipe_stdin[0] = %d, pipe_stdin[1] = %d\n", pipe_stdin[0], pipe_stdin[1]); 19 | //printf("pipe_stdout[0] = %d, pipe_stdout[1] = %d\n", pipe_stdout[0], pipe_stdout[1]); 20 | 21 | p = fork(); 22 | if(p < 0) return p; /* Fork failed */ 23 | if(p == 0) { /* child */ 24 | close(pipe_stdin[1]); 25 | dup2(pipe_stdin[0], 0); 26 | close(pipe_stdout[0]); 27 | dup2(pipe_stdout[1], 1); 28 | execl("/bin/sh", "sh", "-c", cmdline, NULL); 29 | perror("execl"); exit(99); 30 | } 31 | childinfo->child_pid = p; 32 | childinfo->to_child = pipe_stdin[1]; 33 | childinfo->from_child = pipe_stdout[0]; 34 | close(pipe_stdin[0]); 35 | close(pipe_stdout[1]); 36 | return 0; 37 | } 38 | 39 | //#define TESTING 40 | #ifdef TESTING 41 | int main(void) { 42 | char buf[1000]; 43 | struct popen2 kid; 44 | popen2("tr a-z A-Z", &kid); 45 | write(kid.to_child, "testing\n", 8); 46 | close(kid.to_child); 47 | memset(buf, 0, 1000); 48 | read(kid.from_child, buf, 1000); 49 | printf("kill(%d, 0) -> %d\n", kid.child_pid, kill(kid.child_pid, 0)); 50 | printf("from child: %s", buf); 51 | printf("waitpid() -> %d\n", waitpid(kid.child_pid, NULL, 0)); 52 | printf("kill(%d, 0) -> %d\n", kid.child_pid, kill(kid.child_pid, 0)); 53 | return 0; 54 | } 55 | #endif 56 | -------------------------------------------------------------------------------- /writelimiter/popen2.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct popen2 { 4 | pid_t child_pid; 5 | int from_child, to_child; 6 | }; 7 | 8 | int popen2(const char *cmdline, struct popen2 *childinfo); -------------------------------------------------------------------------------- /writelimiter/recv_fd.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #ifndef NULL 5 | #define NULL ((void *) 0) 6 | #endif 7 | 8 | int recv_fd(int socket) 9 | { 10 | int sent_fd; 11 | struct msghdr socket_message; 12 | struct iovec io_vector[1]; 13 | struct cmsghdr *control_message = NULL; 14 | char message_buffer[1]; 15 | char ancillary_element_buffer[CMSG_SPACE(sizeof(int))]; 16 | 17 | /* start clean */ 18 | memset(&socket_message, 0, sizeof(struct msghdr)); 19 | memset(ancillary_element_buffer, 0, CMSG_SPACE(sizeof(int))); 20 | 21 | /* setup a place to fill in message contents */ 22 | io_vector[0].iov_base = message_buffer; 23 | io_vector[0].iov_len = 1; 24 | socket_message.msg_iov = io_vector; 25 | socket_message.msg_iovlen = 1; 26 | 27 | /* provide space for the ancillary data */ 28 | socket_message.msg_control = ancillary_element_buffer; 29 | socket_message.msg_controllen = CMSG_SPACE(sizeof(int)); 30 | 31 | if(recvmsg(socket, &socket_message, MSG_CMSG_CLOEXEC) < 0) 32 | return -1; 33 | 34 | if(message_buffer[0] != 'F') 35 | { 36 | /* this did not originate from the above function */ 37 | return -1; 38 | } 39 | 40 | if((socket_message.msg_flags & MSG_CTRUNC) == MSG_CTRUNC) 41 | { 42 | /* we did not provide enough space for the ancillary element array */ 43 | return -1; 44 | } 45 | 46 | /* iterate ancillary elements */ 47 | for(control_message = CMSG_FIRSTHDR(&socket_message); 48 | control_message != NULL; 49 | control_message = CMSG_NXTHDR(&socket_message, control_message)) 50 | { 51 | if( (control_message->cmsg_level == SOL_SOCKET) && 52 | (control_message->cmsg_type == SCM_RIGHTS) ) 53 | { 54 | sent_fd = *((int *) CMSG_DATA(control_message)); 55 | return sent_fd; 56 | } 57 | } 58 | 59 | return -1; 60 | } 61 | -------------------------------------------------------------------------------- /writelimiter/recv_fd.h: -------------------------------------------------------------------------------- 1 | int recv_fd(int socket); 2 | -------------------------------------------------------------------------------- /writelimiter/safer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int safer_write(int f, char* b, int n) { 5 | int acc = 0; 6 | while(n) { 7 | int r = write(f, b, n); 8 | if(!r) return acc; 9 | if(r==-1) { 10 | if (errno==EINTR || errno==EAGAIN) continue; 11 | return -1; 12 | } 13 | n-=r; 14 | b+=r; 15 | acc+=r; 16 | } 17 | return acc; 18 | } 19 | 20 | int safer_read(int f, char* b, int n) { 21 | int acc = 0; 22 | while(n) { 23 | int r = read(f, b, n); 24 | if(!r) return acc; 25 | if(r==-1) { 26 | if (errno==EINTR || errno==EAGAIN) continue; 27 | return -1; 28 | } 29 | n-=r; 30 | b+=r; 31 | acc+=r; 32 | } 33 | return acc; 34 | } 35 | -------------------------------------------------------------------------------- /writelimiter/send_fd.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #ifndef NULL 5 | #define NULL ((void *) 0) 6 | #endif 7 | 8 | int send_fd(int socket, int fd_to_send) 9 | { 10 | if(fd_to_send==-1) { 11 | return send(socket, "X", 1, 0); 12 | } 13 | struct msghdr socket_message; 14 | struct iovec io_vector[1]; 15 | struct cmsghdr *control_message = NULL; 16 | char message_buffer[1]; 17 | /* storage space needed for an ancillary element with a paylod of length is CMSG_SPACE(sizeof(length)) */ 18 | char ancillary_element_buffer[CMSG_SPACE(sizeof(int))]; 19 | int available_ancillary_element_buffer_space; 20 | 21 | /* at least one vector of one byte must be sent */ 22 | message_buffer[0] = 'F'; 23 | io_vector[0].iov_base = message_buffer; 24 | io_vector[0].iov_len = 1; 25 | 26 | /* initialize socket message */ 27 | memset(&socket_message, 0, sizeof(struct msghdr)); 28 | socket_message.msg_iov = io_vector; 29 | socket_message.msg_iovlen = 1; 30 | 31 | /* provide space for the ancillary data */ 32 | available_ancillary_element_buffer_space = CMSG_SPACE(sizeof(int)); 33 | memset(ancillary_element_buffer, 0, available_ancillary_element_buffer_space); 34 | socket_message.msg_control = ancillary_element_buffer; 35 | socket_message.msg_controllen = available_ancillary_element_buffer_space; 36 | 37 | /* initialize a single ancillary data element for fd passing */ 38 | control_message = CMSG_FIRSTHDR(&socket_message); 39 | control_message->cmsg_level = SOL_SOCKET; 40 | control_message->cmsg_type = SCM_RIGHTS; 41 | control_message->cmsg_len = CMSG_LEN(sizeof(int)); 42 | *((int *) CMSG_DATA(control_message)) = fd_to_send; 43 | 44 | return sendmsg(socket, &socket_message, 0); 45 | } 46 | -------------------------------------------------------------------------------- /writelimiter/send_fd.h: -------------------------------------------------------------------------------- 1 | int send_fd(int socket, int fd_to_send); 2 | -------------------------------------------------------------------------------- /writelimiter/writelimiter: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | POLICY_PROG="$1"; 4 | shift 5 | 6 | if [ -z "$1" ]; then 7 | echo "Usage: ./writelimiter policy_commandline argv...\n" 8 | exit 1 9 | fi 10 | 11 | BANNED_SYSCALLS=" 12 | rmdir,e1 13 | mkdir,e1 14 | creat,e1 15 | unlinkat,e1 16 | openat,e1 17 | renameat,e1 18 | mkdirat,e1 19 | linkat,e1 20 | symlink,e1 21 | link,e1 22 | symlink,e1 23 | chmod,e1 24 | unlink,e1 25 | rename,e1 26 | mknod,e1 27 | mknodat,e1 28 | utime,e1 29 | utimes,e1 30 | utimensat,e1 31 | futimesat,e1 32 | removexattr,e1 33 | fremovexattr,e1 34 | lremovexattr,e1 35 | setxattr,e1 36 | lsetxattr,e1 37 | fsetxattr,e1 38 | lchown,e1 39 | fchmod,e1 40 | fchmodat,e1 41 | fchown,e1 42 | fchownat,e1 43 | chown,e1 44 | " 45 | 46 | if uname -m | grep -qx '^[ix][3-6]\?86$'; then 47 | BANNED_SYSCALLS="$BANNED_SYSCALLS 48 | chown32,e1 49 | fchown32,e1 50 | lchown32,e1 51 | " 52 | fi 53 | 54 | export LIMIT_SYSCALLS_DEFAULT_ACTION=a 55 | 56 | ./writelimiter_broker \ 57 | "$POLICY_PROG" \ 58 | /usr/bin/env \ 59 | LD_PRELOAD=`pwd`/libwritelimiter.so \ 60 | ../limit_syscalls \ 61 | 'open,A1&&0x03==0x01,e1' \ 62 | 'open,A1&&0x03==0x02,e1' \ 63 | 'open,A1&&0x40==0x40,e1' \ 64 | $BANNED_SYSCALLS \ 65 | -- "$@" 66 | -------------------------------------------------------------------------------- /writelimiter/writelimiter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | #define PATH_MAX 4096 5 | 6 | struct request { 7 | char operation; 8 | int flags; 9 | mode_t mode; 10 | dev_t dev; 11 | char pathname[PATH_MAX]; 12 | }; 13 | -------------------------------------------------------------------------------- /writelimiter/writelimiter_broker.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | // Created by Vitaly "_Vi" Shukela; 2013; License=MIT 12 | 13 | //#include 14 | 15 | #include "writelimiter.h" 16 | 17 | #include "recv_fd.h" 18 | #include "send_fd.h" 19 | #include "popen2.h" 20 | 21 | int safer_write(int f, char* b, int n); 22 | int safer_read(int f, char* b, int n); 23 | 24 | 25 | int serve(int socket, int syncsocket, int policy_in, int policy_out) { 26 | char additional_path[PATH_MAX]; 27 | memset(additional_path, 0, PATH_MAX); 28 | int noticket = 0; /* after 'A' operation */ 29 | for(;;) { 30 | if (!noticket) { 31 | write(syncsocket, "grant\n", 6); /* to prevent multiple clients messing with each other */ 32 | } 33 | noticket=0; 34 | 35 | struct request r; 36 | int ret = read(socket, &r, sizeof(r)); 37 | r.pathname[PATH_MAX-1]=0; 38 | 39 | if (ret==0) break; 40 | if (ret==-1 && errno==EINTR) continue; 41 | if (ret==-1 && errno==EAGAIN) continue; 42 | if (ret==-1) break; 43 | 44 | errno=0; 45 | 46 | if(strchr(r.pathname, '\n')) errno=EACCES; 47 | 48 | if (!errno) { 49 | safer_write(policy_in, r.pathname, strlen(r.pathname)); 50 | safer_write(policy_in, "\n", 1); 51 | char reply; 52 | safer_read(policy_out, &reply, 1); 53 | if (reply!='1') { 54 | errno=EACCES; 55 | } 56 | } 57 | 58 | if (errno) { 59 | write(socket, &errno, sizeof(errno)); 60 | continue; 61 | } 62 | 63 | errno=0; 64 | ret=-1; 65 | switch (r.operation) { 66 | case 'o': { 67 | ret = open(r.pathname, r.flags, r.mode); 68 | } break; 69 | case 'u': { 70 | unlink(r.pathname); 71 | } break; 72 | case 'r': { 73 | rmdir(r.pathname); 74 | } break; 75 | case 'A': { 76 | /* Additinal path for moving and linking */ 77 | memcpy(additional_path, r.pathname, PATH_MAX); 78 | noticket=1; /* some operation must follow */ 79 | /* Note: this scheme is unreliable. 80 | Kill sender at this point and get hung broker */ 81 | } break; 82 | case 'l': { 83 | if(!additional_path[0]) errno=EINVAL; 84 | else { 85 | linkat(AT_FDCWD, additional_path, AT_FDCWD, r.pathname, r.flags); 86 | } 87 | } break; 88 | case 's': { 89 | if(!additional_path[0]) errno=EINVAL; 90 | else { 91 | symlink(additional_path, r.pathname); 92 | } 93 | } break; 94 | case 'n': { 95 | if(!additional_path[0]) errno=EINVAL; 96 | else { 97 | rename(additional_path, r.pathname); 98 | } 99 | } break; 100 | case 'm': { 101 | mkdir(r.pathname, r.mode); 102 | } break; 103 | case 'c': { 104 | chmod(r.pathname, r.mode); 105 | } break; 106 | case 'k': { 107 | mknod(r.pathname, r.mode, r.dev); 108 | } break; 109 | default: { 110 | errno = ENOSYS; 111 | } 112 | } // switch 113 | 114 | write(socket, &errno, sizeof(errno)); 115 | 116 | if(r.operation == 'o' && !errno) { 117 | send_fd(socket, ret); 118 | close(ret); 119 | } 120 | } 121 | 122 | return 0; 123 | } 124 | 125 | 126 | int main(int argc, char* argv[], char* envp[]) { 127 | if (argc < 3) { 128 | fprintf(stderr, "Usage: writelimiter_broker policy_commandline ...(argv)\n"); 129 | return 1; 130 | } 131 | 132 | 133 | struct popen2 policyprog; 134 | int ret = popen2(argv[1], &policyprog); 135 | if (ret) { 136 | perror("popen2"); 137 | return 1; 138 | } 139 | 140 | int sockets[2] = {-1, -1}; 141 | int syncsockets[2] = {-1,-1}; 142 | socketpair(AF_UNIX, SOCK_SEQPACKET, 0, syncsockets); 143 | socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets); 144 | if (sockets[0]==-1) { 145 | perror("socketpair"); 146 | return 1; 147 | } 148 | 149 | int childpid = fork(); 150 | 151 | if (!childpid) { 152 | close(sockets[0]); 153 | close(syncsockets[0]); 154 | return serve(sockets[1], syncsockets[1], policyprog.to_child, policyprog.from_child); 155 | } 156 | 157 | close(sockets[1]); 158 | close(syncsockets[1]); 159 | dup2(sockets[0], 33); 160 | dup2(syncsockets[0], 34); 161 | close(sockets[0]); 162 | close(syncsockets[0]); 163 | close(policyprog.from_child); 164 | close(policyprog.to_child); 165 | 166 | execve(argv[2], argv+2, envp); 167 | perror("execve"); 168 | return 127; 169 | } --------------------------------------------------------------------------------