Skip to content

Commit d34d4f9

Browse files
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. Add zfs_snapshot_no_setuid parameter to control whether automatically mounted snapshots have the setuid mount option set or not. this could be considered a partial fix for one of the scenarios mentioned in desired. Reviewed-by: Alexander Motin <mav@FreeBSD.org> Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov> Reviewed-by: Tino Reichardt <milky-zfs@mcmilk.de> Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com> Co-authored-by: Fabian Grünbichler <f.gruenbichler@proxmox.com> Closes openzfs#3963 Closes openzfs#16587
1 parent 86737c5 commit d34d4f9

File tree

16 files changed

+56
-15
lines changed

16 files changed

+56
-15
lines changed

include/os/freebsd/zfs/sys/zfs_ctldir.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ extern "C" {
4040
((zdp)->z_zfsvfs->z_ctldir != NULL))
4141
#define zfs_show_ctldir(zdp) \
4242
(zfs_has_ctldir(zdp) && \
43-
((zdp)->z_zfsvfs->z_show_ctldir))
43+
((zdp)->z_zfsvfs->z_show_ctldir == ZFS_SNAPDIR_VISIBLE))
4444

4545
void zfsctl_create(zfsvfs_t *);
4646
void zfsctl_destroy(zfsvfs_t *);

include/os/freebsd/zfs/sys/zfs_vfsops_os.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ struct zfsvfs {
7676
list_t z_all_znodes; /* all vnodes in the fs */
7777
kmutex_t z_znodes_lock; /* lock for z_all_znodes */
7878
struct zfsctl_root *z_ctldir; /* .zfs directory pointer */
79-
boolean_t z_show_ctldir; /* expose .zfs in the root dir */
79+
uint_t z_show_ctldir; /* how to expose .zfs in the root dir */
8080
boolean_t z_issnap; /* true if this is a snapshot */
8181
boolean_t z_use_fuids; /* version allows fuids */
8282
boolean_t z_replay; /* set during ZIL replay */

include/os/linux/zfs/sys/zfs_ctldir.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
(ZTOZSB(zdp)->z_ctldir != NULL))
4646
#define zfs_show_ctldir(zdp) \
4747
(zfs_has_ctldir(zdp) && \
48-
(ZTOZSB(zdp)->z_show_ctldir))
48+
(ZTOZSB(zdp)->z_show_ctldir == ZFS_SNAPDIR_VISIBLE))
4949

5050
extern int zfs_expire_snapshot;
5151

include/os/linux/zfs/sys/zfs_vfsops_os.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ struct zfsvfs {
110110
kmutex_t z_znodes_lock; /* lock for z_all_znodes */
111111
arc_prune_t *z_arc_prune; /* called by ARC to prune caches */
112112
struct inode *z_ctldir; /* .zfs directory inode */
113-
boolean_t z_show_ctldir; /* expose .zfs in the root dir */
113+
uint_t z_show_ctldir; /* how to expose .zfs in the root dir */
114114
boolean_t z_issnap; /* true if this is a snapshot */
115115
boolean_t z_use_fuids; /* version allows fuids */
116116
boolean_t z_replay; /* set during ZIL replay */

include/sys/zfs_ioctl.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ extern "C" {
5757
*/
5858
#define ZFS_SNAPDIR_HIDDEN 0
5959
#define ZFS_SNAPDIR_VISIBLE 1
60+
#define ZFS_SNAPDIR_DISABLED 2
6061

6162
/*
6263
* Property values for snapdev

man/man4/zfs.4

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1570,6 +1570,15 @@ which have the
15701570
.Em no_root_squash
15711571
option set.
15721572
.
1573+
.It Sy zfs_snapshot_no_setuid Ns = Ns Sy 0 Ns | Ns 1 Pq int
1574+
Whether to disable
1575+
.Em setuid/setgid
1576+
support for snapshot mounts triggered by access to the
1577+
.Sy .zfs/snapshot
1578+
directory by setting the
1579+
.Em nosuid
1580+
mount option.
1581+
.
15731582
.It Sy zfs_flags Ns = Ns Sy 0 Pq int
15741583
Set additional debugging flags.
15751584
The following flags may be bitwise-ored together:

man/man7/zfsconcepts.7

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ File system snapshots can be accessed under the
7171
directory in the root of the file system.
7272
Snapshots are automatically mounted on demand and may be unmounted at regular
7373
intervals.
74-
The visibility of the
74+
The availability and visibility of the
7575
.Pa .zfs
7676
directory can be controlled by the
7777
.Sy snapdir

man/man7/zfsprops.7

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1848,11 +1848,11 @@ Controls whether the volume snapshot devices under
18481848
are hidden or visible.
18491849
The default value is
18501850
.Sy hidden .
1851-
.It Sy snapdir Ns = Ns Sy hidden Ns | Ns Sy visible
1851+
.It Sy snapdir Ns = Ns Sy disabled Ns | Ns Sy hidden Ns | Ns Sy visible
18521852
Controls whether the
18531853
.Pa .zfs
1854-
directory is hidden or visible in the root of the file system as discussed in
1855-
the
1854+
directory is disabled, hidden or visible in the root of the file system as
1855+
discussed in the
18561856
.Sx Snapshots
18571857
section of
18581858
.Xr zfsconcepts 7 .

module/os/freebsd/zfs/zfs_vnops_os.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -774,6 +774,8 @@ zfs_lookup(vnode_t *dvp, const char *nm, vnode_t **vpp,
774774
}
775775
if (zfs_has_ctldir(zdp) && strcmp(nm, ZFS_CTLDIR_NAME) == 0) {
776776
zfs_exit(zfsvfs, FTAG);
777+
if (zfsvfs->z_show_ctldir == ZFS_SNAPDIR_DISABLED)
778+
return (SET_ERROR(ENOENT));
777779
if ((cnp->cn_flags & ISLASTCN) != 0 && nameiop != LOOKUP)
778780
return (SET_ERROR(ENOTSUP));
779781
error = zfsctl_root(zfsvfs, cnp->cn_lkflags, vpp);

module/os/linux/zfs/zfs_ctldir.c

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ static krwlock_t zfs_snapshot_lock;
111111
*/
112112
int zfs_expire_snapshot = ZFSCTL_EXPIRE_SNAPSHOT;
113113
static int zfs_admin_snapshot = 0;
114+
static int zfs_snapshot_no_setuid = 0;
114115

115116
typedef struct {
116117
char *se_name; /* full snapshot name */
@@ -807,7 +808,9 @@ zfsctl_root_lookup(struct inode *dip, const char *name, struct inode **ipp,
807808
if ((error = zfs_enter(zfsvfs, FTAG)) != 0)
808809
return (error);
809810

810-
if (strcmp(name, "..") == 0) {
811+
if (zfsvfs->z_show_ctldir == ZFS_SNAPDIR_DISABLED) {
812+
*ipp = NULL;
813+
} else if (strcmp(name, "..") == 0) {
811814
*ipp = dip->i_sb->s_root->d_inode;
812815
} else if (strcmp(name, ZFS_SNAPDIR_NAME) == 0) {
813816
*ipp = zfsctl_inode_lookup(zfsvfs, ZFSCTL_INO_SNAPDIR,
@@ -1097,9 +1100,9 @@ zfsctl_snapshot_mount(struct path *path, int flags)
10971100
zfsvfs_t *zfsvfs;
10981101
zfsvfs_t *snap_zfsvfs;
10991102
zfs_snapentry_t *se;
1100-
char *full_name, *full_path;
1103+
char *full_name, *full_path, *options;
11011104
char *argv[] = { "/usr/bin/env", "mount", "-i", "-t", "zfs", "-n",
1102-
NULL, NULL, NULL };
1105+
"-o", NULL, NULL, NULL, NULL };
11031106
char *envp[] = { NULL };
11041107
int error;
11051108
struct path spath;
@@ -1113,6 +1116,7 @@ zfsctl_snapshot_mount(struct path *path, int flags)
11131116

11141117
full_name = kmem_zalloc(ZFS_MAX_DATASET_NAME_LEN, KM_SLEEP);
11151118
full_path = kmem_zalloc(MAXPATHLEN, KM_SLEEP);
1119+
options = kmem_zalloc(7, KM_SLEEP);
11161120

11171121
error = zfsctl_snapshot_name(zfsvfs, dname(dentry),
11181122
ZFS_MAX_DATASET_NAME_LEN, full_name);
@@ -1128,6 +1132,9 @@ zfsctl_snapshot_mount(struct path *path, int flags)
11281132
zfsvfs->z_vfs->vfs_mntpoint ? zfsvfs->z_vfs->vfs_mntpoint : "",
11291133
dname(dentry));
11301134

1135+
snprintf(options, 7, "%s",
1136+
zfs_snapshot_no_setuid ? "nosuid" : "suid");
1137+
11311138
/*
11321139
* Multiple concurrent automounts of a snapshot are never allowed.
11331140
* The snapshot may be manually mounted as many times as desired.
@@ -1150,8 +1157,9 @@ zfsctl_snapshot_mount(struct path *path, int flags)
11501157
* value from call_usermodehelper() will be (exitcode << 8 + signal).
11511158
*/
11521159
dprintf("mount; name=%s path=%s\n", full_name, full_path);
1153-
argv[6] = full_name;
1154-
argv[7] = full_path;
1160+
argv[7] = options;
1161+
argv[8] = full_name;
1162+
argv[9] = full_path;
11551163
error = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC);
11561164
if (error) {
11571165
if (!(error & MOUNT_BUSY << 8)) {
@@ -1312,3 +1320,7 @@ MODULE_PARM_DESC(zfs_admin_snapshot, "Enable mkdir/rmdir/mv in .zfs/snapshot");
13121320

13131321
module_param(zfs_expire_snapshot, int, 0644);
13141322
MODULE_PARM_DESC(zfs_expire_snapshot, "Seconds to expire .zfs/snapshot");
1323+
1324+
module_param(zfs_snapshot_no_setuid, int, 0644);
1325+
MODULE_PARM_DESC(zfs_snapshot_no_setuid,
1326+
"Disable setuid/setgid for automounts in .zfs/snapshot");

module/os/linux/zfs/zfs_dir.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,9 @@ zfs_dirlook(znode_t *dzp, char *name, znode_t **zpp, int flags,
415415
*zpp = zp;
416416
rw_exit(&dzp->z_parent_lock);
417417
} else if (zfs_has_ctldir(dzp) && strcmp(name, ZFS_CTLDIR_NAME) == 0) {
418+
if (ZTOZSB(dzp)->z_show_ctldir == ZFS_SNAPDIR_DISABLED) {
419+
return (SET_ERROR(ENOENT));
420+
}
418421
ip = zfsctl_root(dzp);
419422
*zpp = ITOZ(ip);
420423
} else {

module/os/linux/zfs/zfs_vfsops.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1702,6 +1702,11 @@ zfs_vget(struct super_block *sb, struct inode **ipp, fid_t *fidp)
17021702
(object == ZFSCTL_INO_ROOT || object == ZFSCTL_INO_SNAPDIR)) {
17031703
*ipp = zfsvfs->z_ctldir;
17041704
ASSERT(*ipp != NULL);
1705+
1706+
if (zfsvfs->z_show_ctldir == ZFS_SNAPDIR_DISABLED) {
1707+
return (SET_ERROR(ENOENT));
1708+
}
1709+
17051710
if (object == ZFSCTL_INO_SNAPDIR) {
17061711
VERIFY(zfsctl_root_lookup(*ipp, "snapshot", ipp,
17071712
0, kcred, NULL, NULL) == 0);

module/os/linux/zfs/zpl_ctldir.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ zpl_root_iterate(struct file *filp, struct dir_context *ctx)
5757
zfsvfs_t *zfsvfs = ITOZSB(file_inode(filp));
5858
int error = 0;
5959

60+
if (zfsvfs->z_show_ctldir == ZFS_SNAPDIR_DISABLED) {
61+
return (SET_ERROR(ENOENT));
62+
}
63+
6064
if ((error = zpl_enter(zfsvfs, FTAG)) != 0)
6165
return (error);
6266

module/zcommon/zfs_prop.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ zfs_prop_init(void)
238238
static const zprop_index_t snapdir_table[] = {
239239
{ "hidden", ZFS_SNAPDIR_HIDDEN },
240240
{ "visible", ZFS_SNAPDIR_VISIBLE },
241+
{ "disabled", ZFS_SNAPDIR_DISABLED },
241242
{ NULL }
242243
};
243244

@@ -436,7 +437,7 @@ zfs_prop_init(void)
436437
"COMPRESS", compress_table, sfeatures);
437438
zprop_register_index(ZFS_PROP_SNAPDIR, "snapdir", ZFS_SNAPDIR_HIDDEN,
438439
PROP_INHERIT, ZFS_TYPE_FILESYSTEM,
439-
"hidden | visible", "SNAPDIR", snapdir_table, sfeatures);
440+
"disabled | hidden | visible", "SNAPDIR", snapdir_table, sfeatures);
440441
zprop_register_index(ZFS_PROP_SNAPDEV, "snapdev", ZFS_SNAPDEV_HIDDEN,
441442
PROP_INHERIT, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME,
442443
"hidden | visible", "SNAPDEV", snapdev_table, sfeatures);

module/zfs/dsl_prop.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -698,6 +698,10 @@ dsl_prop_set_iuv(objset_t *mos, uint64_t zapobj, const char *propname,
698698
*(uint64_t *)value == ZFS_REDUNDANT_METADATA_NONE)
699699
iuv = B_TRUE;
700700
break;
701+
case ZFS_PROP_SNAPDIR:
702+
if (*(uint64_t *)value == ZFS_SNAPDIR_DISABLED)
703+
iuv = B_TRUE;
704+
break;
701705
default:
702706
break;
703707
}

tests/zfs-tests/include/properties.shlib

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ typeset -a logbias_prop_vals=('latency' 'throughput')
3030
typeset -a primarycache_prop_vals=('all' 'none' 'metadata')
3131
typeset -a redundant_metadata_prop_vals=('all' 'most' 'some' 'none')
3232
typeset -a secondarycache_prop_vals=('all' 'none' 'metadata')
33-
typeset -a snapdir_prop_vals=('hidden' 'visible')
33+
typeset -a snapdir_prop_vals=('disabled' 'hidden' 'visible')
3434
typeset -a sync_prop_vals=('standard' 'always' 'disabled')
3535

3636
typeset -a fs_props=('compress' 'checksum' 'recsize'

0 commit comments

Comments
 (0)