diff --git a/src/basic/chase-symlinks.c b/src/basic/chase-symlinks.c index a0d29427d3..35fa97773b 100644 --- a/src/basic/chase-symlinks.c +++ b/src/basic/chase-symlinks.c @@ -57,6 +57,21 @@ static int log_autofs_mount_point(int fd, const char *path, ChaseSymlinksFlags f strna(n1), path); } +static int log_prohibited_symlink(int fd, ChaseSymlinksFlags flags) { + _cleanup_free_ char *n1 = NULL; + + assert(fd >= 0); + + if (!FLAGS_SET(flags, CHASE_WARN)) + return -EREMCHG; + + (void) fd_get_path(fd, &n1); + + return log_warning_errno(SYNTHETIC_ERRNO(EREMCHG), + "Detected symlink where not symlink is allowed at %s, refusing.", + strna(n1)); +} + int chase_symlinks_at( int dir_fd, const char *path, @@ -291,6 +306,9 @@ int chase_symlinks_at( if (S_ISLNK(st.st_mode) && !((flags & CHASE_NOFOLLOW) && isempty(todo))) { _cleanup_free_ char *destination = NULL; + if (flags & CHASE_PROHIBIT_SYMLINKS) + return log_prohibited_symlink(child, flags); + /* This is a symlink, in this case read the destination. But let's make sure we * don't follow symlinks without bounds. */ if (--max_follow <= 0) diff --git a/src/basic/chase-symlinks.h b/src/basic/chase-symlinks.h index 6ea0b17875..af0fcf155a 100644 --- a/src/basic/chase-symlinks.h +++ b/src/basic/chase-symlinks.h @@ -19,6 +19,7 @@ typedef enum ChaseSymlinksFlags { * Note: this may do an NSS lookup, hence this flag cannot be used in PID 1. */ CHASE_AT_RESOLVE_IN_ROOT = 1 << 8, /* Same as openat2()'s RESOLVE_IN_ROOT flag, symlinks are resolved * relative to the given directory fd instead of root. */ + CHASE_PROHIBIT_SYMLINKS = 1 << 9, /* Refuse all symlinks */ } ChaseSymlinksFlags; bool unsafe_transition(const struct stat *a, const struct stat *b); diff --git a/src/test/test-fs-util.c b/src/test/test-fs-util.c index d0259843b6..4bf0a5daf8 100644 --- a/src/test/test-fs-util.c +++ b/src/test/test-fs-util.c @@ -386,6 +386,15 @@ TEST(chase_symlinks) { assert_se(path_equal(path_startswith(result, p), "usr")); result = mfree(result); + /* Test CHASE_PROHIBIT_SYMLINKS */ + + assert_se(chase_symlinks("top/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG); + assert_se(chase_symlinks("top/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG); + assert_se(chase_symlinks("top/dotdot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG); + assert_se(chase_symlinks("top/dotdot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG); + assert_se(chase_symlinks("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG); + assert_se(chase_symlinks("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG); + cleanup: assert_se(rm_rf(temp, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); }