snapdir: add 'disabled' value to make .zfs inaccessible

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 <f.gruenbichler@proxmox.com>

.zfs: don't return .zfs inode if disabled

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
This commit is contained in:
Fabian Grünbichler 2024-02-14 14:33:44 +01:00
parent 8f2f6cd2ac
commit ab9c8a425c
12 changed files with 29 additions and 9 deletions

View File

@ -45,7 +45,7 @@
(ZTOZSB(zdp)->z_ctldir != NULL)) (ZTOZSB(zdp)->z_ctldir != NULL))
#define zfs_show_ctldir(zdp) \ #define zfs_show_ctldir(zdp) \
(zfs_has_ctldir(zdp) && \ (zfs_has_ctldir(zdp) && \
(ZTOZSB(zdp)->z_show_ctldir)) (ZTOZSB(zdp)->z_show_ctldir == ZFS_SNAPDIR_VISIBLE))
extern int zfs_expire_snapshot; extern int zfs_expire_snapshot;

View File

@ -110,7 +110,7 @@ struct zfsvfs {
kmutex_t z_znodes_lock; /* lock for z_all_znodes */ kmutex_t z_znodes_lock; /* lock for z_all_znodes */
arc_prune_t *z_arc_prune; /* called by ARC to prune caches */ arc_prune_t *z_arc_prune; /* called by ARC to prune caches */
struct inode *z_ctldir; /* .zfs directory inode */ 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_issnap; /* true if this is a snapshot */
boolean_t z_use_fuids; /* version allows fuids */ boolean_t z_use_fuids; /* version allows fuids */
boolean_t z_replay; /* set during ZIL replay */ boolean_t z_replay; /* set during ZIL replay */

View File

@ -57,6 +57,7 @@ extern "C" {
*/ */
#define ZFS_SNAPDIR_HIDDEN 0 #define ZFS_SNAPDIR_HIDDEN 0
#define ZFS_SNAPDIR_VISIBLE 1 #define ZFS_SNAPDIR_VISIBLE 1
#define ZFS_SNAPDIR_DISABLED 2
/* /*
* Property values for snapdev * Property values for snapdev

View File

@ -71,7 +71,7 @@ File system snapshots can be accessed under the
directory in the root of the file system. directory in the root of the file system.
Snapshots are automatically mounted on demand and may be unmounted at regular Snapshots are automatically mounted on demand and may be unmounted at regular
intervals. intervals.
The visibility of the The availability and visibility of the
.Pa .zfs .Pa .zfs
directory can be controlled by the directory can be controlled by the
.Sy snapdir .Sy snapdir

View File

@ -1773,11 +1773,11 @@ Controls whether the volume snapshot devices under
are hidden or visible. are hidden or visible.
The default value is The default value is
.Sy hidden . .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 Controls whether the
.Pa .zfs .Pa .zfs
directory is hidden or visible in the root of the file system as discussed in directory is disabled, hidden or visible in the root of the file system as
the discussed in the
.Sx Snapshots .Sx Snapshots
section of section of
.Xr zfsconcepts 7 . .Xr zfsconcepts 7 .

View File

@ -810,7 +810,9 @@ zfsctl_root_lookup(struct inode *dip, const char *name, struct inode **ipp,
if ((error = zfs_enter(zfsvfs, FTAG)) != 0) if ((error = zfs_enter(zfsvfs, FTAG)) != 0)
return (error); 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; *ipp = dip->i_sb->s_root->d_inode;
} else if (strcmp(name, ZFS_SNAPDIR_NAME) == 0) { } else if (strcmp(name, ZFS_SNAPDIR_NAME) == 0) {
*ipp = zfsctl_inode_lookup(zfsvfs, ZFSCTL_INO_SNAPDIR, *ipp = zfsctl_inode_lookup(zfsvfs, ZFSCTL_INO_SNAPDIR,

View File

@ -415,6 +415,9 @@ zfs_dirlook(znode_t *dzp, char *name, znode_t **zpp, int flags,
*zpp = zp; *zpp = zp;
rw_exit(&dzp->z_parent_lock); rw_exit(&dzp->z_parent_lock);
} else if (zfs_has_ctldir(dzp) && strcmp(name, ZFS_CTLDIR_NAME) == 0) { } 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); ip = zfsctl_root(dzp);
*zpp = ITOZ(ip); *zpp = ITOZ(ip);
} else { } else {

View File

@ -1764,6 +1764,11 @@ zfs_vget(struct super_block *sb, struct inode **ipp, fid_t *fidp)
(object == ZFSCTL_INO_ROOT || object == ZFSCTL_INO_SNAPDIR)) { (object == ZFSCTL_INO_ROOT || object == ZFSCTL_INO_SNAPDIR)) {
*ipp = zfsvfs->z_ctldir; *ipp = zfsvfs->z_ctldir;
ASSERT(*ipp != NULL); ASSERT(*ipp != NULL);
if (zfsvfs->z_show_ctldir == ZFS_SNAPDIR_DISABLED) {
return (SET_ERROR(ENOENT));
}
if (object == ZFSCTL_INO_SNAPDIR) { if (object == ZFSCTL_INO_SNAPDIR) {
VERIFY(zfsctl_root_lookup(*ipp, "snapshot", ipp, VERIFY(zfsctl_root_lookup(*ipp, "snapshot", ipp,
0, kcred, NULL, NULL) == 0); 0, kcred, NULL, NULL) == 0);

View File

@ -57,6 +57,10 @@ zpl_root_iterate(struct file *filp, zpl_dir_context_t *ctx)
zfsvfs_t *zfsvfs = ITOZSB(file_inode(filp)); zfsvfs_t *zfsvfs = ITOZSB(file_inode(filp));
int error = 0; int error = 0;
if (zfsvfs->z_show_ctldir == ZFS_SNAPDIR_DISABLED) {
return (SET_ERROR(ENOENT));
}
if ((error = zpl_enter(zfsvfs, FTAG)) != 0) if ((error = zpl_enter(zfsvfs, FTAG)) != 0)
return (error); return (error);

View File

@ -237,6 +237,7 @@ zfs_prop_init(void)
static const zprop_index_t snapdir_table[] = { static const zprop_index_t snapdir_table[] = {
{ "hidden", ZFS_SNAPDIR_HIDDEN }, { "hidden", ZFS_SNAPDIR_HIDDEN },
{ "visible", ZFS_SNAPDIR_VISIBLE }, { "visible", ZFS_SNAPDIR_VISIBLE },
{ "disabled", ZFS_SNAPDIR_DISABLED },
{ NULL } { NULL }
}; };
@ -428,7 +429,7 @@ zfs_prop_init(void)
"COMPRESS", compress_table, sfeatures); "COMPRESS", compress_table, sfeatures);
zprop_register_index(ZFS_PROP_SNAPDIR, "snapdir", ZFS_SNAPDIR_HIDDEN, zprop_register_index(ZFS_PROP_SNAPDIR, "snapdir", ZFS_SNAPDIR_HIDDEN,
PROP_INHERIT, ZFS_TYPE_FILESYSTEM, 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, zprop_register_index(ZFS_PROP_SNAPDEV, "snapdev", ZFS_SNAPDEV_HIDDEN,
PROP_INHERIT, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME, PROP_INHERIT, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME,
"hidden | visible", "SNAPDEV", snapdev_table, sfeatures); "hidden | visible", "SNAPDEV", snapdev_table, sfeatures);

View File

@ -698,6 +698,10 @@ dsl_prop_set_iuv(objset_t *mos, uint64_t zapobj, const char *propname,
*(uint64_t *)value == ZFS_REDUNDANT_METADATA_NONE) *(uint64_t *)value == ZFS_REDUNDANT_METADATA_NONE)
iuv = B_TRUE; iuv = B_TRUE;
break; break;
case ZFS_PROP_SNAPDIR:
if (*(uint64_t *)value == ZFS_SNAPDIR_DISABLED)
iuv = B_TRUE;
break;
default: default:
break; break;
} }

View File

@ -30,7 +30,7 @@ typeset -a logbias_prop_vals=('latency' 'throughput')
typeset -a primarycache_prop_vals=('all' 'none' 'metadata') typeset -a primarycache_prop_vals=('all' 'none' 'metadata')
typeset -a redundant_metadata_prop_vals=('all' 'most' 'some' 'none') typeset -a redundant_metadata_prop_vals=('all' 'most' 'some' 'none')
typeset -a secondarycache_prop_vals=('all' 'none' 'metadata') 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 sync_prop_vals=('standard' 'always' 'disabled')
typeset -a fs_props=('compress' 'checksum' 'recsize' typeset -a fs_props=('compress' 'checksum' 'recsize'