jart / pledge

OpenBSD APIs ported to Linux userspace using SECCOMP BPF and Landlock LSM
ISC License
93 stars 6 forks source link

Confusing output (no error message) when tty is not pledged but program queries ioctl TIOCGWINSZ #6

Open rsms opened 4 months ago

rsms commented 4 months ago

Busybox programs queries TIOCGWINSZ on startup, meaning benign commands like seq needs to pledge "tty". Failing to do so causes pledge to "silently" fail, printing "Bad system call" (no error messages reported.)

$ pledge -p "stdio" /bin/seq 1
Bad system call
$ pledge -p "stdio tty" /bin/seq 1
1

Original issue:


On a barebones musl (Linux 6.5.8)

$ pledge -p "stdio" /bin/date
Bad system call
$ pledge -p "stdio exec" /bin/date
Mon Apr 29 22:44:12 UTC 2024

The culprit seems to be __pledge_mode & PLEDGE_STDERR_LOGGING

https://github.com/jart/pledge/blob/8693ebe15a30bd4235165ad72a469da29ca067cf/cmd/pledge.c#L841

since if I pass -q to pledge, exec works as expected:

$ pledge -q -p "stdio" /bin/date
Mon Apr 29 22:45:30 UTC 2024

Additionally, passing -k to pledge (sets __pledge_mode = PLEDGE_PENALTY_KILL_PROCESS) also results in "Bad system call", even with -q:

$ pledge -k -p "stdio" /bin/date
Bad system call
$ pledge -k -q -p "stdio" /bin/date
Bad system call

If I disable "implicit exec" by commenting out these lines...

https://github.com/jart/pledge/blob/8693ebe15a30bd4235165ad72a469da29ca067cf/cmd/pledge.c#L838-L843

... execv fails with the expected "Operation not permitted" error message:

$ pledge -p "stdio" /bin/date
/bin/date: execve failed: Operation not permitted

~The least messy fix I've come up with is to always include a SECCOMP_RET_ERRNO filter:~ Edit: I realize now that this patch breaks stuff (as it returns early)

--- a/libc/calls/pledge-linux.c 2023-11-08 18:18:17.000000000 +0000
+++ b/libc/calls/pledge-linux.c 2024-04-29 23:10:44.057225745 +0000
@@ -2129,6 +2129,9 @@
     }
   }

+  sf[0].k = SECCOMP_RET_ERRNO;
+  AppendFilter(&f, PLEDGE(sf));
+
   // now determine what we'll do on sandbox violations
   if (mode & PLEDGE_STDERR_LOGGING) {
     // trapping mode

(Note: /bin/date in this example is a static executable, so sandbox.so is not involved)

Linux 6.5.8 is built with:

CONFIG_HAVE_ARCH_SECCOMP=y
CONFIG_HAVE_ARCH_SECCOMP_FILTER=y
CONFIG_SECCOMP=y
CONFIG_SECCOMP_FILTER=y
rsms commented 4 months ago

Pledging tty and rpath makes it work as well:

$ pledge -p "stdio" /bin/date
Bad system call
$ pledge -p "stdio rpath" /bin/date
Bad system call
$ pledge -p "stdio tty" /bin/date
Bad system call
$ pledge -p "stdio rpath tty" /bin/date
Mon Apr 29 23:36:15 UTC 2024

/bin/date is busybox — perhaps busybox reads the file system and calls e.g. SYS_ioctl TIOCGWINSZ on every invocation..?

Edit: Ah, yes indeed it reads /etc/localtime and queries TIOCGWINSZ:

$ strace /bin/date
execve("/bin/date", ["/bin/date"], 0xffffe81ab850 /* 18 vars */) = 0
set_tid_address(0x3b4a50)               = 29898
getuid()                                = 0
openat(AT_FDCWD, "/etc/localtime", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=114, ...}) = 0
mmap(NULL, 114, PROT_READ, MAP_SHARED, 3, 0) = 0xffff89e4b000
close(3)                                = 0
ioctl(1, TIOCGWINSZ, {ws_row=55, ws_col=100, ws_xpixel=0, ws_ypixel=0}) = 0
writev(1, [{iov_base="Mon Apr 29 23:37:55 UTC 2024", iov_len=28}, {iov_base="\n", iov_len=1}], 2Mon Apr 29 23:37:55 UTC 2024
) = 29
exit_group(0)                           = ?
+++ exited with 0 +++