From ab9c8a425cb202bc451c02e7a1a3ecc1dc8212ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Gr=C3=BCnbichler?= Date: Wed, 14 Feb 2024 14:33:44 +0100 Subject: [PATCH] snapdir: add 'disabled' value to make .zfs inaccessible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit in some environments, just making the .zfs control dir hidden from sight might not be enough. in particular, the following scenarios might warrant not allowing access at all: - old snapshots with wrong permissions/ownership - old snapshots with exploitable setuid/setgid binaries - old snapshots with sensitive contents introducing a new 'disabled' value that not only hides the control dir, but prevents access to its contents by returning ENOENT solves all of the above. the new property value takes advantage of 'iuv' semantics ("ignore unknown value") to automatically fall back to the old default value when a pool is accessed by an older version of ZFS that doesn't yet know about 'disabled' semantics. I think that technically the zfs_dirlook change is enough to prevent access, but preventing lookups and dir entries in an already opened .zfs handle might also be a good idea to prevent races when modifying the property at runtime. Fixes: #3963 Signed-off-by: Fabian Grünbichler .zfs: don't return .zfs inode if disabled Signed-off-by: Fabian Grünbichler --- include/os/linux/zfs/sys/zfs_ctldir.h | 2 +- include/os/linux/zfs/sys/zfs_vfsops_os.h | 2 +- include/sys/zfs_ioctl.h | 1 + man/man7/zfsconcepts.7 | 2 +- man/man7/zfsprops.7 | 6 +++--- module/os/linux/zfs/zfs_ctldir.c | 4 +++- module/os/linux/zfs/zfs_dir.c | 3 +++ module/os/linux/zfs/zfs_vfsops.c | 5 +++++ module/os/linux/zfs/zpl_ctldir.c | 4 ++++ module/zcommon/zfs_prop.c | 3 ++- module/zfs/dsl_prop.c | 4 ++++ tests/zfs-tests/include/properties.shlib | 2 +- 12 files changed, 29 insertions(+), 9 deletions(-) diff --git a/include/os/linux/zfs/sys/zfs_ctldir.h b/include/os/linux/zfs/sys/zfs_ctldir.h index ad16ab5e44..8f18cda295 100644 --- a/include/os/linux/zfs/sys/zfs_ctldir.h +++ b/include/os/linux/zfs/sys/zfs_ctldir.h @@ -45,7 +45,7 @@ (ZTOZSB(zdp)->z_ctldir != NULL)) #define zfs_show_ctldir(zdp) \ (zfs_has_ctldir(zdp) && \ - (ZTOZSB(zdp)->z_show_ctldir)) + (ZTOZSB(zdp)->z_show_ctldir == ZFS_SNAPDIR_VISIBLE)) extern int zfs_expire_snapshot; diff --git a/include/os/linux/zfs/sys/zfs_vfsops_os.h b/include/os/linux/zfs/sys/zfs_vfsops_os.h index b4d5db21f5..7717e7479a 100644 --- a/include/os/linux/zfs/sys/zfs_vfsops_os.h +++ b/include/os/linux/zfs/sys/zfs_vfsops_os.h @@ -110,7 +110,7 @@ struct zfsvfs { kmutex_t z_znodes_lock; /* lock for z_all_znodes */ arc_prune_t *z_arc_prune; /* called by ARC to prune caches */ struct inode *z_ctldir; /* .zfs directory inode */ - boolean_t z_show_ctldir; /* expose .zfs in the root dir */ + uint_t z_show_ctldir; /* how to expose .zfs in the root dir */ boolean_t z_issnap; /* true if this is a snapshot */ boolean_t z_use_fuids; /* version allows fuids */ boolean_t z_replay; /* set during ZIL replay */ diff --git a/include/sys/zfs_ioctl.h b/include/sys/zfs_ioctl.h index 26dfe97604..0839561fc6 100644 --- a/include/sys/zfs_ioctl.h +++ b/include/sys/zfs_ioctl.h @@ -57,6 +57,7 @@ extern "C" { */ #define ZFS_SNAPDIR_HIDDEN 0 #define ZFS_SNAPDIR_VISIBLE 1 +#define ZFS_SNAPDIR_DISABLED 2 /* * Property values for snapdev diff --git a/man/man7/zfsconcepts.7 b/man/man7/zfsconcepts.7 index 1be3d961c3..1d2dff7e48 100644 --- a/man/man7/zfsconcepts.7 +++ b/man/man7/zfsconcepts.7 @@ -71,7 +71,7 @@ File system snapshots can be accessed under the directory in the root of the file system. Snapshots are automatically mounted on demand and may be unmounted at regular intervals. -The visibility of the +The availability and visibility of the .Pa .zfs directory can be controlled by the .Sy snapdir diff --git a/man/man7/zfsprops.7 b/man/man7/zfsprops.7 index 9ff0236f4d..a9449f280e 100644 --- a/man/man7/zfsprops.7 +++ b/man/man7/zfsprops.7 @@ -1773,11 +1773,11 @@ Controls whether the volume snapshot devices under are hidden or visible. The default value is .Sy hidden . -.It Sy snapdir Ns = Ns Sy hidden Ns | Ns Sy visible +.It Sy snapdir Ns = Ns Sy disabled Ns | Ns Sy hidden Ns | Ns Sy visible Controls whether the .Pa .zfs -directory is hidden or visible in the root of the file system as discussed in -the +directory is disabled, hidden or visible in the root of the file system as +discussed in the .Sx Snapshots section of .Xr zfsconcepts 7 . diff --git a/module/os/linux/zfs/zfs_ctldir.c b/module/os/linux/zfs/zfs_ctldir.c index 54ed70d039..4250ce32a3 100644 --- a/module/os/linux/zfs/zfs_ctldir.c +++ b/module/os/linux/zfs/zfs_ctldir.c @@ -810,7 +810,9 @@ zfsctl_root_lookup(struct inode *dip, const char *name, struct inode **ipp, if ((error = zfs_enter(zfsvfs, FTAG)) != 0) return (error); - if (strcmp(name, "..") == 0) { + if (zfsvfs->z_show_ctldir == ZFS_SNAPDIR_DISABLED) { + error = SET_ERROR(ENOENT); + } else if (strcmp(name, "..") == 0) { *ipp = dip->i_sb->s_root->d_inode; } else if (strcmp(name, ZFS_SNAPDIR_NAME) == 0) { *ipp = zfsctl_inode_lookup(zfsvfs, ZFSCTL_INO_SNAPDIR, diff --git a/module/os/linux/zfs/zfs_dir.c b/module/os/linux/zfs/zfs_dir.c index 1eeabe53d2..6e74f42080 100644 --- a/module/os/linux/zfs/zfs_dir.c +++ b/module/os/linux/zfs/zfs_dir.c @@ -415,6 +415,9 @@ zfs_dirlook(znode_t *dzp, char *name, znode_t **zpp, int flags, *zpp = zp; rw_exit(&dzp->z_parent_lock); } else if (zfs_has_ctldir(dzp) && strcmp(name, ZFS_CTLDIR_NAME) == 0) { + if (ZTOZSB(dzp)->z_show_ctldir == ZFS_SNAPDIR_DISABLED) { + return (SET_ERROR(ENOENT)); + } ip = zfsctl_root(dzp); *zpp = ITOZ(ip); } else { diff --git a/module/os/linux/zfs/zfs_vfsops.c b/module/os/linux/zfs/zfs_vfsops.c index 2015c20d73..c93f4e5051 100644 --- a/module/os/linux/zfs/zfs_vfsops.c +++ b/module/os/linux/zfs/zfs_vfsops.c @@ -1764,6 +1764,11 @@ zfs_vget(struct super_block *sb, struct inode **ipp, fid_t *fidp) (object == ZFSCTL_INO_ROOT || object == ZFSCTL_INO_SNAPDIR)) { *ipp = zfsvfs->z_ctldir; ASSERT(*ipp != NULL); + + if (zfsvfs->z_show_ctldir == ZFS_SNAPDIR_DISABLED) { + return (SET_ERROR(ENOENT)); + } + if (object == ZFSCTL_INO_SNAPDIR) { VERIFY(zfsctl_root_lookup(*ipp, "snapshot", ipp, 0, kcred, NULL, NULL) == 0); diff --git a/module/os/linux/zfs/zpl_ctldir.c b/module/os/linux/zfs/zpl_ctldir.c index 8ee7fcecc7..75d3a79620 100644 --- a/module/os/linux/zfs/zpl_ctldir.c +++ b/module/os/linux/zfs/zpl_ctldir.c @@ -57,6 +57,10 @@ zpl_root_iterate(struct file *filp, zpl_dir_context_t *ctx) zfsvfs_t *zfsvfs = ITOZSB(file_inode(filp)); int error = 0; + if (zfsvfs->z_show_ctldir == ZFS_SNAPDIR_DISABLED) { + return (SET_ERROR(ENOENT)); + } + if ((error = zpl_enter(zfsvfs, FTAG)) != 0) return (error); diff --git a/module/zcommon/zfs_prop.c b/module/zcommon/zfs_prop.c index 764993b45e..9762dec733 100644 --- a/module/zcommon/zfs_prop.c +++ b/module/zcommon/zfs_prop.c @@ -237,6 +237,7 @@ zfs_prop_init(void) static const zprop_index_t snapdir_table[] = { { "hidden", ZFS_SNAPDIR_HIDDEN }, { "visible", ZFS_SNAPDIR_VISIBLE }, + { "disabled", ZFS_SNAPDIR_DISABLED }, { NULL } }; @@ -428,7 +429,7 @@ zfs_prop_init(void) "COMPRESS", compress_table, sfeatures); zprop_register_index(ZFS_PROP_SNAPDIR, "snapdir", ZFS_SNAPDIR_HIDDEN, PROP_INHERIT, ZFS_TYPE_FILESYSTEM, - "hidden | visible", "SNAPDIR", snapdir_table, sfeatures); + "disabled | hidden | visible", "SNAPDIR", snapdir_table, sfeatures); zprop_register_index(ZFS_PROP_SNAPDEV, "snapdev", ZFS_SNAPDEV_HIDDEN, PROP_INHERIT, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME, "hidden | visible", "SNAPDEV", snapdev_table, sfeatures); diff --git a/module/zfs/dsl_prop.c b/module/zfs/dsl_prop.c index 99f931cd86..5c5b2428c4 100644 --- a/module/zfs/dsl_prop.c +++ b/module/zfs/dsl_prop.c @@ -698,6 +698,10 @@ dsl_prop_set_iuv(objset_t *mos, uint64_t zapobj, const char *propname, *(uint64_t *)value == ZFS_REDUNDANT_METADATA_NONE) iuv = B_TRUE; break; + case ZFS_PROP_SNAPDIR: + if (*(uint64_t *)value == ZFS_SNAPDIR_DISABLED) + iuv = B_TRUE; + break; default: break; } diff --git a/tests/zfs-tests/include/properties.shlib b/tests/zfs-tests/include/properties.shlib index 3dfb295a40..5a39eb3f36 100644 --- a/tests/zfs-tests/include/properties.shlib +++ b/tests/zfs-tests/include/properties.shlib @@ -30,7 +30,7 @@ typeset -a logbias_prop_vals=('latency' 'throughput') typeset -a primarycache_prop_vals=('all' 'none' 'metadata') typeset -a redundant_metadata_prop_vals=('all' 'most' 'some' 'none') typeset -a secondarycache_prop_vals=('all' 'none' 'metadata') -typeset -a snapdir_prop_vals=('hidden' 'visible') +typeset -a snapdir_prop_vals=('disabled' 'hidden' 'visible') typeset -a sync_prop_vals=('standard' 'always' 'disabled') typeset -a fs_props=('compress' 'checksum' 'recsize'