/* Background. */ Userspace cannot easily resolve a path without resolving symlinks, and would have to manually resolve each path component with O_PATH and O_NOFOLLOW. This is clearly inefficient, and can be fairly easy to screw up (resulting in possible security bugs). Linus has mentioned that Git has a particular need for this kind of flag[1]. It also resolves a fairly long-standing perceived deficiency in O_NOFOLLOw -- that it only blocks the opening of trailing symlinks.
This is part of a refresh of Al's AT_NO_JUMPS patchset[2] (which was a variation on David Drysdale's O_BENEATH patchset[3], which in turn was based on the Capsicum project[4]). /* Userspace API. */ LOOKUP_NO_SYMLINKS will be exposed to userspace through openat2(2). /* Semantics. */ Unlike most other LOOKUP flags (most notably LOOKUP_FOLLOW), LOOKUP_NO_SYMLINKS applies to all components of the path. With LOOKUP_NO_SYMLINKS, any symlink path component encountered during path resolution will yield -ELOOP. If the trailing component is a symlink (and no other components were symlinks), then O_PATH|O_NOFOLLOW will not error out and will instead provide a handle to the trailing symlink -- without resolving it. /* Testing. */ LOOKUP_NO_SYMLINKS is tested as part of the openat2(2) selftests. [1]: https://lore.kernel.org/lkml/CA+55aFyOKM7DW7+0sdDFKdZFXgptb5r1id9=wvhd8agsp7q...@mail.gmail.com/ [2]: https://lore.kernel.org/lkml/20170429220414.gt29...@zeniv.linux.org.uk/ [3]: https://lore.kernel.org/lkml/1415094884-18349-1-git-send-email-drysd...@google.com/ [4]: https://lore.kernel.org/lkml/1404124096-21445-1-git-send-email-drysd...@google.com/ Cc: Christian Brauner <christian.brau...@ubuntu.com> Suggested-by: Al Viro <v...@zeniv.linux.org.uk> Suggested-by: Linus Torvalds <torva...@linux-foundation.org> Signed-off-by: Aleksa Sarai <cyp...@cyphar.com> --- fs/namei.c | 3 +++ include/linux/namei.h | 3 +++ 2 files changed, 6 insertions(+) diff --git a/fs/namei.c b/fs/namei.c index 671c3c1a3425..4e85d6fa4048 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -1045,6 +1045,9 @@ const char *get_link(struct nameidata *nd) int error; const char *res; + if (unlikely(nd->flags & LOOKUP_NO_SYMLINKS)) + return ERR_PTR(-ELOOP); + if (!(nd->flags & LOOKUP_RCU)) { touch_atime(&last->link); cond_resched(); diff --git a/include/linux/namei.h b/include/linux/namei.h index 397a08ade6a2..ee2e35af387f 100644 --- a/include/linux/namei.h +++ b/include/linux/namei.h @@ -39,6 +39,9 @@ enum {LAST_NORM, LAST_ROOT, LAST_DOT, LAST_DOTDOT, LAST_BIND}; #define LOOKUP_ROOT 0x2000 #define LOOKUP_ROOT_GRABBED 0x0008 +/* Scoping flags for lookup. */ +#define LOOKUP_NO_SYMLINKS 0x020000 /* No symlink crossing. */ + extern int path_pts(struct path *path); extern int user_path_at_empty(int, const char __user *, unsigned, struct path *, int *empty); -- 2.23.0