9ns: do not allow binding a symlink onto any other name
authorBarret Rhoden <brho@cs.berkeley.edu>
Wed, 24 Apr 2019 01:07:22 +0000 (21:07 -0400)
committerBarret Rhoden <brho@cs.berkeley.edu>
Wed, 24 Apr 2019 01:59:37 +0000 (21:59 -0400)
commit9413f2fdefd071f9a59ecd78d2b7f8865a9e4ab4
treee75f6a46f81f17db79ce74f64df93db3dc45e85f
parentc2355fc87bb2a6e03c0468e1679eb0db8d79e58d
9ns: do not allow binding a symlink onto any other name

Due to how walk() handles mount points, allowing binds pointing to
symlinks is difficult enough to warrant not supporting that feature.

Specifically, walk() only follows mount points for intermediate parts of
a path, e.g. /this/this/and/this/but-not-this.  If we allowed binding to
symlinks, any of those mounts could be symlinks.  The intermediate ones
are fine, but since walk() does not call domount() and follow the last
path name, we could be sitting on a symlink when walk() returns.

This might not be a huge problem, but all callers of walk() would need
to handle this - in particular namec().  Most everyone calls domount(),
so it is tempting to have domount() call walk_symlink().  However, note
the existing of undomount() (used by dotdot).  You can undo a mount, but
you can't undo a symlink follow.  So I bet you could concoct a situation
where you wanted to undomount(), but the mount/bind was to a symlink and
you're busted.

Given that binds and symlinks are largely independent implementations of
a similar concept, I don't see a need to allow a bind to point to a
symlink.

The naming around binding and symlinks can be a little confusing.  To be
clear, the scenario is:

$ ln -s file symlink
$ /bin/bind symlink /some/other/name

Both of these are in the style:
PROGRAM ACTUAL-THING-WE-WANT LOCATION-FOR-THING

- ln -s calls it TARGET and LINK_NAME
- Our bind calls it SRC_PATH and ONTO_PATH
- Plan 9's bind called it NEW OLD
- Linux's mount --bind calls it OLD NEW
- Another way to think of this is WHAT WHERE, especially when you think
  of binding devices into the namespace.

This means that when you walk to /some/other/name, you'll get the
symlink, and with the various 'follow' flags set, you'd expect to get
'file'.  Prior to this commit, walk() would get the symlink, not the
file.  Now, the bind just fails.

Note you can still bind 'from' a symlink, i.e. ONTO_PATH.  So you could
do this:

$ ln -s file1 symlink
$ cat symlink # get file1's contents
$ /bin/bind file2 symlink
$ cat symlink # get file2's contents

The file 'symlink' is still a symlink, but 'file2' was bound over it in
the namespace.  Someone else's namespace will still see symlink ->
file1.  If you do a stat or ls -l in the namespace with the bind, you'll
just see the info for 'file2'.

Reported-by: syzbot+2b1a1f196f3612be7660@syzkaller.appspotmail.com
Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
kern/src/ns/chan.c