cr4bd@labunix01:~$ cp myprogram.exe /bin/ls
cp: cannot create regular file ‘/bin/ls’: Permission denied
programs have limited privileges
OS tracks ‘‘user’’ of running every program
result: malware I installed shouldn’t be able to effect other users
idea 1: reuse this support for web browsers
can’t make whole browser run as ‘‘different user’’
how about just the parts that are ‘‘dangerous’’?
simple example: want to show videos
video decoding library is tens of thousands of lines of code
what does video decoding library do?
setup: create new user
start video decoder as new user
communicate via ‘‘pipes’’
/* dangerous video decoder to isolate */
int main() {
/* switch to right user */
SetUserTo("user-without-privileges"));
while (fread(videoData, sizeof(videoData), 1, stdin) > 0) {
doDangerousVideoDecoding(videoData, imageData);
fwrite(imageData, sizeof(imageData), 1, stdout);
}
}
/* code that uses it */
FILE *fh = RunProgramAndGetFileHandle("./video-decoder");
for (;;) {
fwrite(getNextVideoData(), SIZE, 1, fh);
fread(image, sizeof(image), 1, fh);
displayImage(image);
}‘‘other user’’ can still do too much
read unprotected files
write temporary files?
open network connections
use all your memory
…
awkward to do
switching users requires special permissions
seperate user for each video decoder, audio decoder, web page renderer?
slowdown — extra copying
primary way application talks to OS: system calls
function calls that request OS do something
typically: how program can interact with rest of system
result of filtering operations called a ‘‘sandbox’’
idea: attacker can play in sandbox as much as they want
can’t do anything ‘‘harmful’’
other possible implementations:
prctl(SECCOMP_SET_MODE_STRICT, 0, 0)
read, write, _exit, and sigreturn. Other system calls [kill the program].’’when statically linked:
execve("./hello_world", ["./hello_world"], 0x7ffeb4127f70 /* 28 vars */) = 0
brk(NULL) = 0x22f8000
brk(0x22f91c0) = 0x22f91c0
arch_prctl(ARCH_SET_FS, 0x22f8880) = 0
uname({sysname="Linux", nodename="reiss-t3620", ...}) = 0
readlink("/proc/self/exe", "/u/cr4bd/spring2023/cs3130/slide"..., 4096) = 57
brk(0x231a1c0) = 0x231a1c0
brk(0x231b000) = 0x231b000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 4), ...}) = 0
write(1, "Hello, World!\n", 14) = 14
exit_group(0) = ?
+++ exited with 0 +++
execve: run program
brk: allocate heap space
arch_prctl(ARCH_SET_FS, …): thread local storage pointer
uname: get system information
readlink of /proc/self/exe: get name of this program
access: can we access this file [in this case, a config file]?
fstat: get information about open file
exit_group: variant of exit
$ strace ...
... [startup stuff, not shown] ...
openat(AT_FDCWD, "output.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
newfstatat(3, "", {st_mode=S_IFREG|0664, st_size=0, ...}, AT_EMPTY_PATH) = 0
write(3, "example", 7) = 7
close(3) = 0
$ strace ...
... [startup stuff, not shown] ...
futex(0x73f0bd640ba4, FUTEX_WAKE_PRIVATE, 2147483647) = 0
...
openat(AT_FDCWD, "/usr/lib/ssl/openssl.cnf", O_RDONLY) = 3
...
sysinfo({...}) = 0
...
socket(AF_INET6, SOCK_DGRAM, IPPROTO_IP) = 3
close(3) = 0
socketpair(AF_UNIX, SOCK_STREAM, 0, [3, 4]) = 0
fcntl(3, F_GETFL) = 0x2 (flags O_RDWR)
fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK) = 0
...
rt_sigaction(SIGPIPE, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
...
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 5
setsockopt(5, SOL_TCP, TCP_NODELAY, [1], 4) = 0
...
getrandom("\xd6\x8c\xc3\x42\x07\x92"..., 48, 0) = 48
...
Linux supports more fine-grained system call filtering
using BPF (Berkeley Packet Filter) programming language
can check system call argument values, but…
// memory[offset of "nr"] --> accumulator
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, nr))),
// if (accumulator == SYS_write) PC += 1
BPF_STMT(BPF_JMP | BPF_JEQ, BPF_K, SYS_write, 1, 0),
// return "kill process"
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS),
// return "allow"
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
// memory[offset of "nr"] --> accumulator
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, nr))),
// if (accumulator == SYS_write) PC += 1 else PC += 0
BPF_STMT(BPF_JMP | BPF_JEQ, BPF_K, SYS_write, 1, 0),
// return "kill process"
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS),
// memory[offset of args[0]] --> accumulator
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, args[0]))),
// if (accumulator == 2) PC += 1 else PC += 0
BPF_STMT(BPF_JMP | BPF_JEQ, BPF_K, 2, 1, 0),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
arithmetic (add, or, xor, …)
in eBPF (extended BPF): 10 additional registers
#define CHECK(x) if (!(x)) handle_error();
...
scmp_filter_ctx filter = seccomp_init(SCMP_ACT_KILL_PROCESS);
CHECK(seccomp_rule_add(filter, SCMP_ACT_ALLOW, SCMP_SYS(read), 0) == 0);
CHECK(seccomp_rule_add(filter, SCMP_ACT_ALLOW, SCMP_SYS(write), 0) == 0);
CHECK(seccomp_load(filter) == 0);
open("foo.txt", O_RDONLY);
parameters:
O_RDONLY’’)very problematic to filter using BPF interface
can deal with using ‘ptrace’ — Linux debugging interface
problem 2: filter language doesn’t allow reading pointers
problem 3: string can be changed from another core
problem 4: several other syscalls (that might be used innocently)
often programs do operations by talking to ‘‘server’’ program
whole extra set of calls to sanitize
also, server programs might have security problems
requests need filtering:
problem:
files go to Download directory only
can’t choose arbitary filenames
browser “kernel” displays file chooser
only permits files selected by user
OpenSSH uses privilege seperation for its SSH server
what you log into on portal
separate network processing code from authentication code
seperate process per connection — users don’t share
developed before system call filtering was widely available
sandboxed process tells ‘‘monitor’’ to:
perform cryptographic operations
ask to switch to user — if given user password, etc.
after authentication: new process running as logged-in user
large application changes
lots of application knowledge
better application design anyways?
capabilities = program has list of things it can access
most common thing with design: open files
used as basis of some operating system designs
in ‘‘fully’’ capability-based OSes also…
my Linux desktop has two disks:
/ — an SSD/mnt/extradisk — a hard drivehard drive appears as subdirectory of SSD
subdirectory called a mount point
on Unix: each process tracks its own root directory (/)
can be changed with chroot() system call
chrootusage: can isolate program from other files on system
# mkdir /tmp/example
# cp /bin/ls /tmp/example/ls
# chroot /tmp/example /ls
chroot: failed to run command ‘/ls’: No such file or directory
# cp -r /lib64 /tmp/example/lib64
# mkdir -p /tmp/example/lib
# cp -r /lib/x86_64-linux-gnu /tmp/example/lib/x86_64-linux-gnu
# chroot /tmp/example /ls
/ls: error while loading shared libraries: libpcre2-8.so.0: cannot open shared object file: No such file or directory
# cp /usr/lib/x86_64-linux-gnu/libpcre2-8* /tmp/example/lib/x86_64-linux-gnu
# chroot /tmp/example /ls /
lib lib64 ls
# chroot /tmp/example /ls /..
lib lib64 ls
#
chroot /tmp/example /ls >tmp.txt
/ls runnning in /tmp/example (only open /tmp/example)int dir_fd = open("/tmp/other", O_PATH);
// change root to /tmp/example, cd to /
if (0 != chroot("/tmp/example")) handle_error();
if (0 != chdir("/")) handle_error();
// access /tmp/other/other.txt through old open directory
int other_txt_fd = openat(dir_fd, "other.txt", O_RDONLY);
read(other_txt_fd, ...);
// access /tmp/outside.txt through old open directory
int outside_fd = openat(dir_fd, "../outside.txt", O_RDONLY);
read(other_fd, ...);/* mount special "sysfs" (system info) filesystem */
mount("none", "/sys", "sysfs", 0, 0);
/* retrieve device number of first SSD */
int fd = open("/sys/class/nvme/nvme0/nvme0n1/dev", O_RDONLY);
char device_node[10] = {0};
read(fd, device_node, sizeof(device_node));
close(fd);
int major, minor;
sscanf(device_node, "%d:%d", &major, &minor);
/* create "device file for first SSD" */
mknod("/first_ssd", 0777 | S_IFBLK, makedev(major, minor));
/* access SSD data */
fd = open("/first_ssd", O_RDWR);
read(fd, ...);some things make chroot impractical in general:
seems like one needs extra copies of most of the system
hard to communicate between separate roots
requires administrator permissions to configure
sudowhat scenarios does chroot make most/least sense for?
int id = clone(start_function, ..., CLONE_NEWUSER | other-flags);unshare(CLONE_NEWUSER);Linux: users identified by numerical user IDs (UIDs)
with user namespaces:
control file /proc/PROCESS-ID/UID_MAP contains lines like:
0 1000 2 — UID 0–1 maps to UID 1000–10011000 2000 100 — UID 1000-1100 maps to UID 2000–2100can write to that file to reconfigure (if enough permissions)
mount namespaces:
different idea of what filesystems are available
can be setup with bind mounts to ‘‘real FS’’
from command line:
# runs shell (/bin/sh) in new mount namesapce
shell1$ unshare --mount /bin/sh
# setup directories in /tmp/workdir and make them aliases of things on normal FS
# these aliases will only exist for processes in mount namespace
shell2$ mkdir -p /tmp/workdir/bin
shell2$ mkdir -p /tmp/workdir/lib
shell2$ mkdir -p /tmp/workdir/usr
shell2$ mkdir -p /tmp/workdir/current
shell2$ mount -o bind,ro /bin /tmp/workdir/bin
shell2$ mount -o bind,ro /lib /tmp/workdir/lib
shell2$ mount -o bind,ro /usr /tmp/workdir/usr
shell2$ mount -o bind /home/someuser /tmp/workdir/current
# start new shell with the root directory being /tmp/workdir
shell2$ chroot /tmp/workdir /bin/sh
shell3$ cd /
shell3$ /bin/ls
bin current lib usr
user namespace and mount namespace together:
run program in new user namespace
map regular root (in namespace) to regular user
move to new mount namespace
setup bind mounts + chroot
run program with subset of available files
other resources with namespaces
network — common usage: virtual network device for set processes
hostname (‘‘UTS’’)
process identifiers
control groups (resource limits for memory, CPU usage, disk I/O, etc.)
control groups — tied to namespaces
primarily: CPU/memory/IO performance restrictions
also mechanism for adding IO device restrictions
also mechanism to start/stop a bunch of processes together
docker, lxc, lxd, containerd
bubblewrap, firejail
SELinux’s sandbox
Linux’s seccomp + namespaces + SELinux commonly used to implement containers
usual goal: looks like virtual machine, but much lower overhead
examples: Docker, Kubernetes
can disable access to /proc/PID/exe (and related things)
system call: prctl(PR_SET_DUMPABLE, 0)
but… the run-in-container tool did this for a while
problem: this gets reset on executing a new program
and attacker could make the new program be /proc/PID/exe
but change dynamic linking setup to run attacker code
… which accesses /proc/self/exe
make single-use copy of start-in-container tool each time command run
… so modifying it doesn’t change anything
other solutions:
recall: renderer communicates with ‘browser kernel’
‘browser kernel’ might have bugs in this interface
Chrome Windows sandbox
based on ‘‘restricted access tokens’’
https://googleprojectzero.blogspot.com/2020/04/you-wont-believe-what-this-one-line.html:
problem: Windows erronously allowed starting new processes with a more unrestricted token
tricky to get unrestricted token — but could duplicate another process’s (oops!)
which whole-application sandboxing technique seems better for
(full answer: could mix techniques + probably depends on details of app)
A. chroot + system call filtering
B. chroot + mount and user namespaces
C. virtual machine dedicated to application
D. SELinux-like mandatory access control
so far: relying on OS features for sandboxing
good reasons:
but problems with relying on OS:
‘dynamic’ language virtual machine, like Java VM, .Net CLR
virtual machine targetted for C/C++-like code, like WebAssembly
assembly-to-assembly conversion
WebAssembly: language virtual machine specification intended…
to be compiled to from C/C++
to be easy to just-in-time compile to native machine code
to be run in web browsers (fast web apps)
WebAssembly virtual machine code designed to be validated before running
allows for efficient interpreters or conversion to assembly
language specification very explicit about what needs to be checked at runtime
check that instructions have right number of operands available
2 + 2 into 2 2 +)check operands that can be checked (constants)
check the calls go to only functions listed in table
check the branches go to only locations listed in table, and only within one function
saw interfaces for using sandboxes from user perspective?
what about for privilege separation?
some reusable tools have appeared for this (but no clear winner)
one example: RLBox (published in Usenix Security 2020)
part of example from author’s presentation:
autosandbox = rlbox::create_sandbox<wasm>();
tainted<jpeg_decompress_struct*> p_jpeg_img = sandbox.malloc_in_sandbox<jpeg_decompress_struct>();
tainted<jpeg_source_mgr*> p_jpeg_input_source_mgr = sandbox.malloc_in_sandbox<jpeg_source_mgr>();
sandbox.invoke(jpeg_create_decompress, p_jpeg_img);
p_jpeg_img->src = p_jpeg_input_source_mgr;
p_jpeg_img->src->fill_input_buffer = ...;
sandbox.invoke(jpeg_read_header,p_jpeg_img/*...*/);
tool handles running ‘jpeg_create_decompress’, ‘jpeg_read_header’ in sandbox
values shared with sandbox marked as ‘‘tainted’’
this example: using WebAssembly-based sandbox
used in firefox
OS X (tries to) implement system call filtering
main challenge: what about files?
OS X solution: OS service displays file-open dialog
application can ask to remember file was chosen previously
not chosen/remembered — can’t access
Qubes: heavily sandboxed OS
runs seperate VMs instead of filtering syscalls
UI that clearly shows what VM each window is from
advantage: easier to gaurentee isolation
disadvantage: harder to share between VMs
disadvantage: much more runtime overhead
from Clark et al, ‘‘No Time At All: Opportunity Cost of Android Permissions’’ (HotWireless’16)
Felt, Chin, Hanna, Song and Wagner, ‘‘Android Permissions Demystified’’ (CCS 2011)
used static analysis to compare requested permissions to what applications did
sample of 900 applications
estimate approx 200 over-privileged
selected from Felt et al’s analysis:
developers confused similar permissions
ACCESS_NETWORK_STATE versus ACCESS_WIFI_STATEdevelopers thought permissions were needed for delegated tasks
CALL_PHONE not needed to invoke phone appINSTALL_APPLICATION not needed to open app store install dialogdevelopers thought permissions needed for all methods of class
WRITE_SETTINGS when using (no-permission) read-settings operationscopy-and-paste
same paper did survey about what permissions meant
three multiple choice questions
302 respondents; 3 fully correct
average 21%
from Felt et al, ‘‘How To Ask For Permission’’ (HotSec’12)
Felt et al list ‘‘principles’’:
‘‘Conserve user attention, utilizaing it for only permissions that have severe consquences’’
‘‘When possible, avoid interrupting the user’s primary task with explicit security decisions’’
the two permissions:
can hide window content while user interacts with it
… and stealthy get user to do more things
permissions check limited API calls for getting private info,…
… but there were alternative, unfiltered system calls for
getting MAC address (effectively phone ID)
ioctl system call on socketWiFi base station address
location
advertising libraries would store phone ID/account info in a file
and would read phone ID/account info from a file
Security Enhanced Linux
‘‘Mandatory Access Control’’ system for the Linux
not necessairily run in mandatory control mode
programs run in particular ‘‘domain’’
objects (files, port numbers, other programs, etc.) can be assigned labels
rules about what labels programs are allowed to access
$ ls -Z /var/log/lastlog
-rw-r--r--. root root system_u:object_r:lastlog_t:s0 /var/log/lastlog
$ chcon --type=newtype_t some_file
$ semanage fcontext --add --type web_files_t '/var/www/html(/.*)?'
$ restorecon -R -v /var/www/html
define(`read_files_pattern',`
allow $1 $2:dir search_dir_perms;
allow $1 $3:file read_file_perms;
')
...
define(`read_lnk_files_pattern',`
allow $1 $2:dir search_dir_perms;
allow $1 $3:lnk_file read_lnk_file_perms;
')
...
allow httpd_t httpd_config_t:dir list_dir_perms;
read_files_pattern(httpd_t, httpd_config_t, httpd_config_t)
read_lnk_files_pattern(httpd_t, httpd_config_t, httpd_config_t)
confining whole browsers was hard
but maybe we can do this for simpler applications?
idea 1: applications send system calls to OS
example: video player VLC playing a local file on my laptop
uses 73 unique kinds of system calls
opens many files that are not the video file
sandboxed applications want to access display server
which option seems best for security/performance?
saw interfaces for using sandboxes from user perspective?
what about for privilege separation?
some reusable tools have appeared for this (but no clear winner)
one example: RLBox (published in Usenix Security 2020)
part of example from author’s presentation:
autosandbox = rlbox::create_sandbox<wasm>();
tainted<jpeg_decompress_struct*> p_jpeg_img = sandbox.malloc_in_sandbox<jpeg_decompress_struct>();
tainted<jpeg_source_mgr*> p_jpeg_input_source_mgr = sandbox.malloc_in_sandbox<jpeg_source_mgr>();
sandbox.invoke(jpeg_create_decompress, p_jpeg_img);
p_jpeg_img->src = p_jpeg_input_source_mgr;
p_jpeg_img->src->fill_input_buffer = ...;
sandbox.invoke(jpeg_read_header,p_jpeg_img/*...*/);
tool handles running ‘jpeg_create_decompress’, ‘jpeg_read_header’ in sandbox
values shared with sandbox marked as ‘‘tainted’’
this example: using WebAssembly-based sandbox
used in firefox