diff options
author | Pawel Jakub Dawidek <[email protected]> | 2022-02-03 14:37:57 -0800 |
---|---|---|
committer | Tony Hutter <[email protected]> | 2022-02-16 17:58:55 -0800 |
commit | 3e27b589cff711a8c3cca659678d48361ff4e33b (patch) | |
tree | 12cc0a6b86b43651fc035659d87a316dc6fc81e7 /tests/zfs-tests | |
parent | 9221ff1888c9d8b80ffd9c45b44231ad19c0e9fb (diff) |
Fix clearing set-uid and set-gid bits on a file when replying a write
POSIX requires that set-uid and set-gid bits to be removed when an
unprivileged user writes to a file and ZFS does that during normal
operation.
The problem arrises when the write is stored in the ZIL and replayed.
During replay we have no access to original credentials of the process
doing the write, so zfs_write() will be performed with the root
credentials. When root is doing the write set-uid and set-gid bits
are not removed from the file.
To correct that, log a separate TX_SETATTR entry that removed those bits
on first write to such file.
Idea from: Christian Schwarz
Add test for ZIL replay of setuid/setgid clearing.
Improve various edge cases when clearing setid bits:
- The setid bits can be readded during a single write, so make sure to check
for them on every chunk write.
- Log TX_SETATTR record at most once per transaction group (if the setid bits
are keep coming back).
- Move zfs_log_setattr() outside of zp->z_acl_lock.
Reviewed-by: Dan McDonald <[email protected]>
Reviewed-by: Brian Behlendorf <[email protected]>
Co-authored-by: Christian Schwarz <[email protected]>
Signed-off-by: Pawel Jakub Dawidek <[email protected]>
Closes #13027
Diffstat (limited to 'tests/zfs-tests')
7 files changed, 179 insertions, 79 deletions
diff --git a/tests/zfs-tests/tests/functional/suid/Makefile.am b/tests/zfs-tests/tests/functional/suid/Makefile.am index 594d2b77c..0145c1205 100644 --- a/tests/zfs-tests/tests/functional/suid/Makefile.am +++ b/tests/zfs-tests/tests/functional/suid/Makefile.am @@ -7,6 +7,7 @@ dist_pkgdata_SCRIPTS = \ suid_write_to_sgid.ksh \ suid_write_to_suid_sgid.ksh \ suid_write_to_none.ksh \ + suid_write_zil_replay.ksh \ cleanup.ksh \ setup.ksh diff --git a/tests/zfs-tests/tests/functional/suid/suid_write_to_file.c b/tests/zfs-tests/tests/functional/suid/suid_write_to_file.c index 571dc553b..f3febb903 100644 --- a/tests/zfs-tests/tests/functional/suid/suid_write_to_file.c +++ b/tests/zfs-tests/tests/functional/suid/suid_write_to_file.c @@ -29,86 +29,16 @@ #include <sys/stat.h> #include <unistd.h> #include <fcntl.h> - -static void -test_stat_mode(mode_t extra) -{ - struct stat st; - int i, fd; - char fpath[1024]; - char *penv[] = {"TESTDIR", "TESTFILE0"}; - char buf[] = "test"; - mode_t res; - mode_t mode = 0777 | extra; - - /* - * Get the environment variable values. - */ - for (i = 0; i < sizeof (penv) / sizeof (char *); i++) { - if ((penv[i] = getenv(penv[i])) == NULL) { - fprintf(stderr, "getenv(penv[%d])\n", i); - exit(1); - } - } - - umask(0); - if (stat(penv[0], &st) == -1 && mkdir(penv[0], mode) == -1) { - perror("mkdir"); - exit(2); - } - - snprintf(fpath, sizeof (fpath), "%s/%s", penv[0], penv[1]); - unlink(fpath); - if (stat(fpath, &st) == 0) { - fprintf(stderr, "%s exists\n", fpath); - exit(3); - } - - fd = creat(fpath, mode); - if (fd == -1) { - perror("creat"); - exit(4); - } - close(fd); - - if (setuid(65534) == -1) { - perror("setuid"); - exit(5); - } - - fd = open(fpath, O_RDWR); - if (fd == -1) { - perror("open"); - exit(6); - } - - if (write(fd, buf, sizeof (buf)) == -1) { - perror("write"); - exit(7); - } - close(fd); - - if (stat(fpath, &st) == -1) { - perror("stat"); - exit(8); - } - unlink(fpath); - - /* Verify SUID/SGID are dropped */ - res = st.st_mode & (0777 | S_ISUID | S_ISGID); - if (res != (mode & 0777)) { - fprintf(stderr, "stat(2) %o\n", res); - exit(9); - } -} +#include <stdbool.h> int main(int argc, char *argv[]) { - const char *name; + const char *name, *phase; mode_t extra; + struct stat st; - if (argc < 2) { + if (argc < 3) { fprintf(stderr, "Invalid argc\n"); exit(1); } @@ -127,7 +57,77 @@ main(int argc, char *argv[]) exit(1); } - test_stat_mode(extra); + const char *testdir = getenv("TESTDIR"); + if (!testdir) { + fprintf(stderr, "getenv(TESTDIR)\n"); + exit(1); + } + + umask(0); + if (stat(testdir, &st) == -1 && mkdir(testdir, 0777) == -1) { + perror("mkdir"); + exit(2); + } + + char fpath[1024]; + snprintf(fpath, sizeof (fpath), "%s/%s", testdir, name); + + + phase = argv[2]; + if (strcmp(phase, "PRECRASH") == 0) { + + /* clean up last run */ + unlink(fpath); + if (stat(fpath, &st) == 0) { + fprintf(stderr, "%s exists\n", fpath); + exit(3); + } + + int fd; + + fd = creat(fpath, 0777 | extra); + if (fd == -1) { + perror("creat"); + exit(4); + } + close(fd); + + if (setuid(65534) == -1) { + perror("setuid"); + exit(5); + } + + fd = open(fpath, O_RDWR); + if (fd == -1) { + perror("open"); + exit(6); + } + + const char buf[] = "test"; + if (write(fd, buf, sizeof (buf)) == -1) { + perror("write"); + exit(7); + } + close(fd); + + } else if (strcmp(phase, "REPLAY") == 0) { + /* created in PRECRASH run */ + } else { + fprintf(stderr, "Invalid phase %s\n", phase); + exit(1); + } + + if (stat(fpath, &st) == -1) { + perror("stat"); + exit(8); + } + + /* Verify SUID/SGID are dropped */ + mode_t res = st.st_mode & (0777 | S_ISUID | S_ISGID); + if (res != 0777) { + fprintf(stderr, "stat(2) %o\n", res); + exit(9); + } return (0); } diff --git a/tests/zfs-tests/tests/functional/suid/suid_write_to_none.ksh b/tests/zfs-tests/tests/functional/suid/suid_write_to_none.ksh index dd0197861..470350f96 100755 --- a/tests/zfs-tests/tests/functional/suid/suid_write_to_none.ksh +++ b/tests/zfs-tests/tests/functional/suid/suid_write_to_none.ksh @@ -47,6 +47,6 @@ function cleanup log_onexit cleanup log_note "Verify write(2) to regular file by non-owner" -log_must $STF_SUITE/tests/functional/suid/suid_write_to_file "NONE" +log_must $STF_SUITE/tests/functional/suid/suid_write_to_file "NONE" "PRECRASH" log_pass "Verify write(2) to regular file by non-owner passed" diff --git a/tests/zfs-tests/tests/functional/suid/suid_write_to_sgid.ksh b/tests/zfs-tests/tests/functional/suid/suid_write_to_sgid.ksh index 49ae2bd1b..3c95a4026 100755 --- a/tests/zfs-tests/tests/functional/suid/suid_write_to_sgid.ksh +++ b/tests/zfs-tests/tests/functional/suid/suid_write_to_sgid.ksh @@ -47,6 +47,6 @@ function cleanup log_onexit cleanup log_note "Verify write(2) to SGID file by non-owner" -log_must $STF_SUITE/tests/functional/suid/suid_write_to_file "SGID" +log_must $STF_SUITE/tests/functional/suid/suid_write_to_file "SGID" "PRECRASH" log_pass "Verify write(2) to SGID file by non-owner passed" diff --git a/tests/zfs-tests/tests/functional/suid/suid_write_to_suid.ksh b/tests/zfs-tests/tests/functional/suid/suid_write_to_suid.ksh index 3983aad2e..4183cbeef 100755 --- a/tests/zfs-tests/tests/functional/suid/suid_write_to_suid.ksh +++ b/tests/zfs-tests/tests/functional/suid/suid_write_to_suid.ksh @@ -47,6 +47,6 @@ function cleanup log_onexit cleanup log_note "Verify write(2) to SUID file by non-owner" -log_must $STF_SUITE/tests/functional/suid/suid_write_to_file "SUID" +log_must $STF_SUITE/tests/functional/suid/suid_write_to_file "SUID" "PRECRASH" log_pass "Verify write(2) to SUID file by non-owner passed" diff --git a/tests/zfs-tests/tests/functional/suid/suid_write_to_suid_sgid.ksh b/tests/zfs-tests/tests/functional/suid/suid_write_to_suid_sgid.ksh index a058c7e7d..f7a08a55f 100755 --- a/tests/zfs-tests/tests/functional/suid/suid_write_to_suid_sgid.ksh +++ b/tests/zfs-tests/tests/functional/suid/suid_write_to_suid_sgid.ksh @@ -47,6 +47,6 @@ function cleanup log_onexit cleanup log_note "Verify write(2) to SUID/SGID file by non-owner" -log_must $STF_SUITE/tests/functional/suid/suid_write_to_file "SUID_SGID" +log_must $STF_SUITE/tests/functional/suid/suid_write_to_file "SUID_SGID" "PRECRASH" log_pass "Verify write(2) to SUID/SGID file by non-owner passed" diff --git a/tests/zfs-tests/tests/functional/suid/suid_write_zil_replay.ksh b/tests/zfs-tests/tests/functional/suid/suid_write_zil_replay.ksh new file mode 100755 index 000000000..81f431f6b --- /dev/null +++ b/tests/zfs-tests/tests/functional/suid/suid_write_zil_replay.ksh @@ -0,0 +1,99 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +. $STF_SUITE/tests/functional/slog/slog.kshlib + +verify_runnable "global" + +function cleanup_fs +{ + cleanup +} + +log_assert "Verify ZIL replay results in correct SUID/SGID bits for unprivileged write to SUID/SGID files" +log_onexit cleanup_fs +log_must setup + +# +# 1. Create a file system (TESTFS) +# +log_must zpool destroy "$TESTPOOL" +log_must zpool create $TESTPOOL $VDEV log mirror $LDEV +log_must zfs set compression=on $TESTPOOL +log_must zfs create -o mountpoint="$TESTDIR" $TESTPOOL/$TESTFS + +# Make all the writes from suid_write_to_file.c sync +log_must zfs set sync=always "$TESTPOOL/$TESTFS" + +# +# This dd command works around an issue where ZIL records aren't created +# after freezing the pool unless a ZIL header already exists. Create a file +# synchronously to force ZFS to write one out. +# +log_must dd if=/dev/zero of=$TESTDIR/sync \ + conv=fdatasync,fsync bs=1 count=1 + +# +# 2. Freeze TESTFS +# +log_must zpool freeze $TESTPOOL + +# +# 3. Unprivileged write to a setuid file +# +log_must $STF_SUITE/tests/functional/suid/suid_write_to_file "NONE" "PRECRASH" +log_must $STF_SUITE/tests/functional/suid/suid_write_to_file "SUID" "PRECRASH" +log_must $STF_SUITE/tests/functional/suid/suid_write_to_file "SGID" "PRECRASH" +log_must $STF_SUITE/tests/functional/suid/suid_write_to_file "SUID_SGID" "PRECRASH" + +# +# 4. Unmount filesystem and export the pool +# +# At this stage TESTFS is empty again and frozen, the intent log contains +# a complete set of deltas to replay. +# +log_must zfs unmount $TESTPOOL/$TESTFS + +log_note "List transactions to replay:" +log_must zdb -iv $TESTPOOL/$TESTFS + +log_must zpool export $TESTPOOL + +# +# 5. Remount TESTFS <which replays the intent log> +# +# Import the pool to unfreeze it and claim log blocks. It has to be +# `zpool import -f` because we can't write a frozen pool's labels! +# +log_must zpool import -f -d $VDIR $TESTPOOL + +log_must $STF_SUITE/tests/functional/suid/suid_write_to_file "NONE" "REPLAY" +log_must $STF_SUITE/tests/functional/suid/suid_write_to_file "SUID" "REPLAY" +log_must $STF_SUITE/tests/functional/suid/suid_write_to_file "SGID" "REPLAY" +log_must $STF_SUITE/tests/functional/suid/suid_write_to_file "SUID_SGID" "REPLAY" + +log_pass |