CVE-2021-3996: Files or Directories Accessible to External Parties (CWE-552)
value: A logic error was found in the libmount library of util-linux in the function that allows an unprivileged user to unmount a FUSE filesystem. This flaw allows a local user on a vulnerable system to unmount other users' filesystems that are either world-writable themselves (like /tmp) or mounted in a world-writable directory. An attacker may use this flaw to cause a denial of service to applications that use the affected filesystems.
CWE description: The product makes files or directories accessible to unauthorized actors, even though they should not be.
CVSS v3 base score: 5.5
Published: 2022-08-23T20:15Z
Repository: https://github.com/util-linux/util-linux
Language: C
Files changed: libmount/docs/libmount-sections.txt, libmount/src/fs.c, libmount/src/libmount.h.in, libmount/src/libmount.sym, libmount/src/mountP.h, libmount/src/tab_parse.c, misc-utils/findmnt.c
Commit date: 2022-01-04 10:37:55 +0100
Commit message: libmount: remove support for deleted mount table entries

The "(deleted)" suffix has been originally used by kernel for deleted
mountpoints. Since kernel commit 9d4d65748a5ca26ea8650e50ba521295549bf4e3
(Dec 2014) kernel does not use this suffix for mount stuff in /proc at
all. Let's remove this support from libmount too.

Signed-off-by: Karel Zak <kzak@redhat.com>

Diff: diff --git a/libmount/docs/libmount-sections.txt b/libmount/docs/libmount-sections.txt
index 7961dd420..097e2f066 100644
--- a/libmount/docs/libmount-sections.txt
+++ b/libmount/docs/libmount-sections.txt
@@ -244,11 +244,10 @@ mnt_fs_get_tid
 mnt_fs_get_usedsize
 mnt_fs_get_userdata
 mnt_fs_get_user_options
 mnt_fs_get_vfs_options
 mnt_fs_get_vfs_options_all
-mnt_fs_is_deleted
 mnt_fs_is_kernel
 mnt_fs_is_netfs
 mnt_fs_is_pseudofs
 mnt_fs_is_regularfs
 mnt_fs_is_swaparea
diff --git a/libmount/src/fs.c b/libmount/src/fs.c
index 163f70bd3..b44d310ee 100644
--- a/libmount/src/fs.c
+++ b/libmount/src/fs.c
@@ -649,21 +649,10 @@ int mnt_fs_is_regularfs(struct libmnt_fs *fs)
 	return !(mnt_fs_is_pseudofs(fs)
 		 || mnt_fs_is_netfs(fs)
 		 || mnt_fs_is_swaparea(fs));
 }
 
-/**
- * mnt_fs_is_deleted:
- * @fs: filesystem
- *
- * Returns: 1 if the filesystem target is a marked as deleted by kernel
- */
-int mnt_fs_is_deleted(struct libmnt_fs *fs)
-{
-	return mnt_fs_get_flags(fs) & MNT_FS_DELETED;
-}
-
 /**
  * mnt_fs_get_fstype:
  * @fs: fstab/mtab/mountinfo entry pointer
  *
  * Returns: pointer to filesystem type.
diff --git a/libmount/src/libmount.h.in b/libmount/src/libmount.h.in
index a96778754..3bcf74682 100644
--- a/libmount/src/libmount.h.in
+++ b/libmount/src/libmount.h.in
@@ -510,11 +510,10 @@ extern int mnt_fs_match_source(struct libmnt_fs *fs, const char *source,
 extern int mnt_fs_match_fstype(struct libmnt_fs *fs, const char *types);
 extern int mnt_fs_match_options(struct libmnt_fs *fs, const char *options);
 extern int mnt_fs_print_debug(struct libmnt_fs *fs, FILE *file);
 
 extern int mnt_fs_is_kernel(struct libmnt_fs *fs);
-extern int mnt_fs_is_deleted(struct libmnt_fs *fs);
 extern int mnt_fs_is_swaparea(struct libmnt_fs *fs);
 extern int mnt_fs_is_netfs(struct libmnt_fs *fs);
 extern int mnt_fs_is_pseudofs(struct libmnt_fs *fs);
 extern int mnt_fs_is_regularfs(struct libmnt_fs *fs);
 
diff --git a/libmount/src/libmount.sym b/libmount/src/libmount.sym
index 15b738394..aa96091b1 100644
--- a/libmount/src/libmount.sym
+++ b/libmount/src/libmount.sym
@@ -363,7 +363,6 @@ MOUNT_2_37 {
 } MOUNT_2_35;
 
 
 MOUNT_2_38 {
 	mnt_fs_is_regularfs;
-	mnt_fs_is_deleted;
 } MOUNT_2_37;
diff --git a/libmount/src/mountP.h b/libmount/src/mountP.h
index a4e1c6206..b47640daa 100644
--- a/libmount/src/mountP.h
+++ b/libmount/src/mountP.h
@@ -226,11 +226,10 @@ struct libmnt_fs {
 #define MNT_FS_PSEUDO	(1 << 1) /* pseudo filesystem */
 #define MNT_FS_NET	(1 << 2) /* network filesystem */
 #define MNT_FS_SWAP	(1 << 3) /* swap device */
 #define MNT_FS_KERNEL	(1 << 4) /* data from /proc/{mounts,self/mountinfo} */
 #define MNT_FS_MERGED	(1 << 5) /* already merged data from /run/mount/utab */
-#define MNT_FS_DELETED	(1 << 6) /* target path in mountinfo contains "(deleted)" */
 
 /*
  * mtab/fstab/mountinfo file
  */
 struct libmnt_table {
diff --git a/libmount/src/tab_parse.c b/libmount/src/tab_parse.c
index d94233ca9..4407f9c9c 100644
--- a/libmount/src/tab_parse.c
+++ b/libmount/src/tab_parse.c
@@ -225,17 +225,10 @@ static int mnt_parse_mountinfo_line(struct libmnt_fs *fs, const char *s)
 	if (!fs->target) {
 		DBG(TAB, ul_debug("tab parse error: [target]"));
 		goto fail;
 	}
 
-	/* remove "\040(deleted)" suffix */
-	p = (char *) endswith(fs->target, PATH_DELETED_SUFFIX);
-	if (p && *p) {
-		*p = '\0';
-		fs->flags |= MNT_FS_DELETED;
-	}
-
 	s = skip_separator(s);
 
 	/* (6) vfs options (fs-independent) */
 	fs->vfs_optstr = unmangle(s, &s);
 	if (!fs->vfs_optstr) {
diff --git a/misc-utils/findmnt.c b/misc-utils/findmnt.c
index 4fcd11700..1c19d608b 100644
--- a/misc-utils/findmnt.c
+++ b/misc-utils/findmnt.c
@@ -53,11 +53,10 @@
 
 /* column IDs */
 enum {
 	COL_ACTION,
 	COL_AVAIL,
-	COL_DELETED,
 	COL_FREQ,
 	COL_FSROOT,
 	COL_FSTYPE,
 	COL_FS_OPTIONS,
 	COL_ID,
@@ -101,11 +100,10 @@ struct colinfo {
 
 /* columns descriptions (don't use const, this is writable) */
 static struct colinfo infos[] = {
 	[COL_ACTION]       = { "ACTION",         10, SCOLS_FL_STRICTWIDTH, N_("action detected by --poll") },
 	[COL_AVAIL]        = { "AVAIL",           5, SCOLS_FL_RIGHT, N_("filesystem size available") },
-	[COL_DELETED]      = { "DELETED",         1, SCOLS_FL_RIGHT, N_("filesystem target marked as deleted") },
 	[COL_FREQ]         = { "FREQ",            1, SCOLS_FL_RIGHT, N_("dump(8) period in days [fstab only]") },
 	[COL_FSROOT]       = { "FSROOT",       0.25, SCOLS_FL_NOEXTREMES, N_("filesystem root") },
 	[COL_FSTYPE]       = { "FSTYPE",       0.10, SCOLS_FL_TRUNC, N_("filesystem type") },
 	[COL_FS_OPTIONS]   = { "FS-OPTIONS",   0.10, SCOLS_FL_TRUNC, N_("FS specific mount options") },
 	[COL_ID]           = { "ID",              2, SCOLS_FL_RIGHT, N_("mount ID") },
@@ -675,13 +673,10 @@ static char *get_data(struct libmnt_fs *fs, int num)
 		break;
 	case COL_PASSNO:
 		if (!mnt_fs_is_kernel(fs))
 			xasprintf(&str, "%d", mnt_fs_get_passno(fs));
 		break;
-	case COL_DELETED:
-		str = xstrdup(mnt_fs_is_deleted(fs) ? "1" : "0");
-		break;
 	default:
 		break;
 	}
 	return str;
 }
@@ -1031,13 +1026,10 @@ static int match_func(struct libmnt_fs *fs,
 		mnt_fs_get_table(fs, &tb);
 		if (tb && mnt_table_over_fs(tb, fs, NULL) != 0)
 			return rc;
 	}
 
-	if ((flags & FL_DELETED) && !mnt_fs_is_deleted(fs))
-		return rc;
-
 	return !rc;
 }
 
 /* iterate over filesystems in @tb */
 struct libmnt_fs *get_next_fs(struct libmnt_table *tb,
@@ -1302,11 +1294,10 @@ static void __attribute__((__noreturn__)) usage(void)
 	fputs(_(" -A, --all              disable all built-in filters, print all filesystems\n"), out);
 	fputs(_(" -a, --ascii            use ASCII chars for tree formatting\n"), out);
 	fputs(_(" -b, --bytes            print sizes in bytes rather than in human readable format\n"), out);
 	fputs(_(" -C, --nocanonicalize   don't canonicalize when comparing paths\n"), out);
 	fputs(_(" -c, --canonicalize     canonicalize printed paths\n"), out);
-	fputs(_("     --deleted          print filesystems with mountpoint marked as deleted\n"), out);
 	fputs(_(" -D, --df               imitate the output of df(1)\n"), out);
 	fputs(_(" -d, --direction <word> direction of search, 'forward' or 'backward'\n"), out);
 	fputs(_(" -e, --evaluate         convert tags (LABEL,UUID,PARTUUID,PARTLABEL) \n"
 	        "                          to device names\n"), out);
 	fputs(_(" -F, --tab-file <path>  alternative file for -s, -m or -k options\n"), out);
@@ -1371,20 +1362,18 @@ int main(int argc, char *argv[])
 		FINDMNT_OPT_TREE,
 		FINDMNT_OPT_OUTPUT_ALL,
 		FINDMNT_OPT_PSEUDO,
 		FINDMNT_OPT_REAL,
 		FINDMNT_OPT_VFS_ALL,
-		FINDMNT_OPT_SHADOWED,
-		FINDMNT_OPT_DELETED,
+		FINDMNT_OPT_SHADOWED
 	};
 
 	static const struct option longopts[] = {
 		{ "all",	    no_argument,       NULL, 'A'		 },
 		{ "ascii",	    no_argument,       NULL, 'a'		 },
 		{ "bytes",	    no_argument,       NULL, 'b'		 },
 		{ "canonicalize",   no_argument,       NULL, 'c'		 },
-		{ "deleted",        no_argument,       NULL, FINDMNT_OPT_DELETED },
 		{ "direction",	    required_argument, NULL, 'd'		 },
 		{ "df",		    no_argument,       NULL, 'D'		 },
 		{ "evaluate",	    no_argument,       NULL, 'e'		 },
 		{ "first-only",	    no_argument,       NULL, 'f'		 },
 		{ "fstab",	    no_argument,       NULL, 's'		 },
@@ -1599,13 +1588,10 @@ int main(int argc, char *argv[])
 			flags |= FL_VFS_ALL;
 			break;
 		case FINDMNT_OPT_SHADOWED:
 			flags |= FL_SHADOWED;
 			break;
-		case FINDMNT_OPT_DELETED:
-			flags |= FL_DELETED;
-			break;
 		case 'h':
 			usage();
 		case 'V':
 			print_version(EXIT_SUCCESS);
 		default:
@@ -1774,13 +1760,10 @@ int main(int argc, char *argv[])
 			case COL_FREQ:
 			case COL_PASSNO:
 			case COL_TID:
 				scols_column_set_json_type(cl, SCOLS_JSON_NUMBER);
 				break;
-			case COL_DELETED:
-				scols_column_set_json_type(cl, SCOLS_JSON_BOOLEAN);
-				break;
 			default:
 				if (fl & SCOLS_FL_WRAP)
 					scols_column_set_json_type(cl, SCOLS_JSON_ARRAY_STRING);
 				else
 					scols_column_set_json_type(cl, SCOLS_JSON_STRING);

Vulnerable code: mnt_fs_is_deleted
/**
 * mnt_fs_is_deleted:
 * @fs: filesystem
 *
 * Returns: 1 if the filesystem target is a marked as deleted by kernel
 */
int mnt_fs_is_deleted(struct libmnt_fs *fs)
{
	return mnt_fs_get_flags(fs) & MNT_FS_DELETED;
}

extern int mnt_fs_is_deleted(struct libmnt_fs *fs);
	mnt_fs_is_deleted;
#define MNT_FS_DELETED	(1 << 6) /* target path in mountinfo contains "(deleted)" */
	/* remove "\040(deleted)" suffix */
	p = (char *) endswith(fs->target, PATH_DELETED_SUFFIX);
	if (p && *p) {
		*p = '\0';
		fs->flags |= MNT_FS_DELETED;
	}

	COL_DELETED,
	[COL_DELETED]      = { "DELETED",         1, SCOLS_FL_RIGHT, N_("filesystem target marked as deleted") },
	case COL_DELETED:
		str = xstrdup(mnt_fs_is_deleted(fs) ? "1" : "0");
		break;
	if ((flags & FL_DELETED) && !mnt_fs_is_deleted(fs))
		return rc;

	fputs(_("     --deleted          print filesystems with mountpoint marked as deleted\n"), out);
		FINDMNT_OPT_SHADOWED,
		FINDMNT_OPT_DELETED,
		{ "deleted",        no_argument,       NULL, FINDMNT_OPT_DELETED },
		case FINDMNT_OPT_DELETED:
			flags |= FL_DELETED;
			break;
			case COL_DELETED:
				scols_column_set_json_type(cl, SCOLS_JSON_BOOLEAN);
				break;
Fixed code: 		FINDMNT_OPT_SHADOWED