fchmod not working
- Found in: plash_1.18.svn.2008-04-27-1520-1hardy1_amd64 on Ubuntu hardy
From Thomas Leonard's post:
For some reason, unzip and cpio no longer work. Using the HelloWorld.zip in the 0launch/tests directory:
$ pola-run --log -fw / -e unzip -q -o HelloWorld.zip [...] #1: [r.] stat: HelloWorld: ok #1: [r.] stat: HelloWorld: ok #1: [r.] stat: HelloWorld/main: ok #1: [r.] lstat: HelloWorld/main: ok #1: [r.] stat: HelloWorld/main: ok #1: [w.] unlink: HelloWorld/main: ok #1: [w.] open: HelloWorld/main, flags=0o1102, mode=0o666: ok #1: [w.] utime: HelloWorld/main: ok chmod (file attributes) error: Operation not permitted #1 end
Running strace on unzip without pola-run shows:
...
unlink("HelloWorld/main") = 0
open("HelloWorld/main", O_RDWR|O_CREAT|O_TRUNC, 0666) = 12
write(12, "#!/bin/sh\necho Hello World\n", 27) = 27
fchmod(12, 0100755) = 0
close(12) = 0
utime("HelloWorld/main", [2008/02/07-19:49:37, 2005/09/17-14:19:23]) = 0
...
Discussion
The cause is simple: fchmod() is one of those operations for which file descriptors do not behave like capabilities (see FileDescriptorsAsCapabilities). fchmod() only works if the process is running under the UID that owns the inode. So fchmod() has never worked under Plash.
I am surprised that this has not caused a problem before. Perhaps the behaviour of unzip or cpio has changed in Ubuntu hardy. Some programs have been switching to FD-based operations to avoid race conditions such as symlink races. (That tends to involve using openat() or using fchdir() + open() on single-component pathnames, although unzip is clearly not doing that here.) It might have been possible to set the file mode in the open() call, except that open()'s mode argument is restricted by umask, whereas chmod()'s mode is not.
It's a bit odd that unzip is passing a mode of 0100755. 0100000 is S_IFREG (for regular files) as returned by stat(). I don't see anything in the man pages to say chmod() accepts this flag, but I guess it does under Linux.
It's annoying that we have to deal with permissions bits at all. Plash does not normally interpret them. Under Plash, a file does not need the executable bit set to be invoked as an executable via execve(). However, outside of Plash, Unix does check the executable bit, even though this does not usually enforce anything. So it is useful if Plash-sandboxed processes can set the executable bit, as a way to interact with the outside world.
Possible solution #1
The ServerProcess could provide a generic fchmod() operation that takes an FD and a mode and invokes fchmod() under its own UID. This would be very coarse-grained. This limits our ability to restrict chmod() operations on files that we have granted to sandboxed processes.
fchmod() does not normally look at the FD's open flags; you can use it on read-only FDs. We would probably have to check the open flags using fcntl() (F_GETFL).
Possible solution #2
In PlashGlibc, make each file FD backed by a file object (let's call it a ChmodFacet) with a chmod() method.
A similar trick is already applied to DirectoryFDs, but these are not used very often.
It is quite distasteful to have to do this for file FDs. There would be a performance impact:
- open(): not too bad, since interprocess communication is already required.
- close(): now requires an RPC.
- in between: storage cost on server.
However, we wouldn't have to pay these costs if we don't need the chmod() feature. A file object such as a read-only proxy can opt not to return a ChmodFacet from its file_open() method.
Currently these backing objects are not propagated across execve(), fork(), dup()/dup2() or sendmsg(). dup() is easy; fork() a little harder; execve() harder still but doable; but sendmsg() is impractical.
