Skip to content

Commit 9cd47e4

Browse files
committed
Validate mountpoint on path-based unmount using statx
Use statx to verify that path-based unmounts proceed only if the mountpoint reported by statx matches the MNTTAB entry reported by libzfs, aborting the operation if they differ. Align `zfs umount /path` behavior with `zfs umount dataset`. Signed-off-by: Ameer Hamza <ahamza@ixsystems.com>
1 parent 8170eb6 commit 9cd47e4

File tree

5 files changed

+89
-6
lines changed

5 files changed

+89
-6
lines changed

cmd/zfs/zfs_main.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7729,6 +7729,7 @@ unshare_unmount_path(int op, char *path, int flags, boolean_t is_manual)
77297729
struct extmnttab entry;
77307730
const char *cmdname = (op == OP_SHARE) ? "unshare" : "unmount";
77317731
ino_t path_inode;
7732+
char *zfs_mntpnt, *entry_mntpnt;
77327733

77337734
/*
77347735
* Search for the given (major,minor) pair in the mount table.
@@ -7770,6 +7771,24 @@ unshare_unmount_path(int op, char *path, int flags, boolean_t is_manual)
77707771
goto out;
77717772
}
77727773

7774+
/*
7775+
* If the filesystem is mounted, check that the mountpoint matches
7776+
* the one in the mnttab entry w.r.t. provided path. If it doesn't,
7777+
* then we should not proceed further.
7778+
*/
7779+
entry_mntpnt = strdup(entry.mnt_mountp);
7780+
if (zfs_is_mounted(zhp, &zfs_mntpnt)) {
7781+
if (strcmp(zfs_mntpnt, entry_mntpnt) != 0) {
7782+
(void) fprintf(stderr, gettext("cannot %s '%s': "
7783+
"not an original mountpoint\n"), cmdname, path);
7784+
free(zfs_mntpnt);
7785+
free(entry_mntpnt);
7786+
goto out;
7787+
}
7788+
free(zfs_mntpnt);
7789+
}
7790+
free(entry_mntpnt);
7791+
77737792
if (op == OP_SHARE) {
77747793
char nfs_mnt_prop[ZFS_MAXPROPLEN];
77757794
char smbshare_prop[ZFS_MAXPROPLEN];

config/user-statx.m4

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
dnl #
2+
dnl # Check for statx() function and STATX_MNT_ID availability
3+
dnl #
4+
AC_DEFUN([ZFS_AC_CONFIG_USER_STATX], [
5+
AC_CHECK_HEADERS([linux/stat.h],
6+
[have_stat_headers=yes],
7+
[have_stat_headers=no])
8+
9+
AS_IF([test "x$have_stat_headers" = "xyes"], [
10+
AC_CHECK_FUNC([statx], [
11+
AC_DEFINE([HAVE_STATX], [1], [statx() is available])
12+
13+
dnl Check for STATX_MNT_ID availability
14+
AC_MSG_CHECKING([for STATX_MNT_ID])
15+
AC_COMPILE_IFELSE([
16+
AC_LANG_PROGRAM([[
17+
#include <linux/stat.h>
18+
]], [[
19+
struct statx stx;
20+
int mask = STATX_MNT_ID;
21+
(void)mask;
22+
(void)stx.stx_mnt_id;
23+
]])
24+
], [
25+
AC_MSG_RESULT([yes])
26+
AC_DEFINE([HAVE_STATX_MNT_ID], [1], [STATX_MNT_ID is available])
27+
], [
28+
AC_MSG_RESULT([no])
29+
])
30+
])
31+
], [
32+
AC_MSG_WARN([linux/stat.h not found; skipping statx support])
33+
])
34+
]) dnl end AC_DEFUN

config/user.m4

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ AC_DEFUN([ZFS_AC_CONFIG_USER], [
3232
ZFS_AC_CONFIG_USER_MAKEDEV_IN_SYSMACROS
3333
ZFS_AC_CONFIG_USER_MAKEDEV_IN_MKDEV
3434
ZFS_AC_CONFIG_USER_ZFSEXEC
35+
ZFS_AC_CONFIG_USER_STATX
3536
3637
AC_CHECK_FUNCS([execvpe issetugid mlockall strlcat strlcpy gettid])
3738

lib/libspl/include/os/linux/sys/stat.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@
3131

3232
#include <sys/mount.h> /* for BLKGETSIZE64 */
3333

34+
#ifdef HAVE_STATX
35+
#include <fcntl.h>
36+
#include <linux/stat.h>
37+
#endif
38+
3439
/*
3540
* Emulate Solaris' behavior of returning the block device size in fstat64().
3641
*/

lib/libspl/os/linux/getmntany.c

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,21 @@ _sol_getmntent(FILE *fp, struct mnttab *mgetp)
8585
}
8686

8787
static int
88-
getextmntent_impl(FILE *fp, struct extmnttab *mp)
88+
getextmntent_impl(FILE *fp, struct extmnttab *mp, uint64_t *mnt_id)
8989
{
9090
int ret;
9191
struct stat64 st;
9292

93+
*mnt_id = 0;
9394
ret = _sol_getmntent(fp, (struct mnttab *)mp);
9495
if (ret == 0) {
96+
#ifdef HAVE_STATX_MNT_ID
97+
struct statx stx;
98+
if (statx(AT_FDCWD, mp->mnt_mountp,
99+
AT_STATX_SYNC_AS_STAT | AT_SYMLINK_NOFOLLOW,
100+
STATX_MNT_ID, &stx) == 0 && (stx.stx_mask & STATX_MNT_ID))
101+
*mnt_id = stx.stx_mnt_id;
102+
#endif
95103
if (stat64(mp->mnt_mountp, &st) != 0) {
96104
mp->mnt_major = 0;
97105
mp->mnt_minor = 0;
@@ -110,6 +118,12 @@ getextmntent(const char *path, struct extmnttab *entry, struct stat64 *statbuf)
110118
struct stat64 st;
111119
FILE *fp;
112120
int match;
121+
boolean_t have_mnt_id = B_FALSE;
122+
uint64_t target_mnt_id = 0;
123+
uint64_t entry_mnt_id;
124+
#ifdef HAVE_STATX_MNT_ID
125+
struct statx stx;
126+
#endif
113127

114128
if (strlen(path) >= MAXPATHLEN) {
115129
(void) fprintf(stderr, "invalid object; pathname too long\n");
@@ -128,6 +142,13 @@ getextmntent(const char *path, struct extmnttab *entry, struct stat64 *statbuf)
128142
return (-1);
129143
}
130144

145+
#ifdef HAVE_STATX_MNT_ID
146+
if (statx(AT_FDCWD, path, AT_STATX_SYNC_AS_STAT | AT_SYMLINK_NOFOLLOW,
147+
STATX_MNT_ID, &stx) == 0 && (stx.stx_mask & STATX_MNT_ID)) {
148+
have_mnt_id = B_TRUE;
149+
target_mnt_id = stx.stx_mnt_id;
150+
}
151+
#endif
131152

132153
if ((fp = fopen(MNTTAB, "re")) == NULL) {
133154
(void) fprintf(stderr, "cannot open %s\n", MNTTAB);
@@ -139,12 +160,15 @@ getextmntent(const char *path, struct extmnttab *entry, struct stat64 *statbuf)
139160
*/
140161

141162
match = 0;
142-
while (getextmntent_impl(fp, entry) == 0) {
143-
if (makedev(entry->mnt_major, entry->mnt_minor) ==
144-
statbuf->st_dev) {
145-
match = 1;
146-
break;
163+
while (getextmntent_impl(fp, entry, &entry_mnt_id) == 0) {
164+
if (have_mnt_id) {
165+
match = (entry_mnt_id == target_mnt_id);
166+
} else {
167+
match = makedev(entry->mnt_major, entry->mnt_minor) ==
168+
statbuf->st_dev;
147169
}
170+
if (match)
171+
break;
148172
}
149173
(void) fclose(fp);
150174

0 commit comments

Comments
 (0)