CVE-2021-3995 (published 2022-08-23T20:15Z)

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 an unprivileged local attacker to unmount FUSE filesystems that belong to certain other users who have a UID that is a prefix of the UID of the attacker in its string form. An attacker may use this flaw to cause a denial of service to applications that use the affected filesystems.

CVSS v3 base score: 5.5, MEDIUM (CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H)

Weakness information:
cwe_id: CWE-552
cwe_name: Files or Directories Accessible to External Parties
description: The product makes files or directories accessible to unauthorized actors, even though they should not be.
extended_description: 
	   Web servers, FTP servers, and similar servers may store a set of files underneath a "root" directory that is accessible to the server's users.  Applications may store sensitive files underneath this root without also using access control to limit which users may request those files, if any.  Alternately, an application might package multiple files or directories into an archive file (e.g., ZIP or tar), but the application might not exclude sensitive files that are underneath those directories.
	   In cloud technologies and containers, this weakness might present itself in the form of misconfigured storage accounts that can be read or written by a public or anonymous user.
	 
         

Fix information:
repo_url: https://github.com/util-linux/util-linux
commit_details: repo_url: https://github.com/util-linux/util-linux
author: Karel Zak
author_date: 2021-11-24 13:53:25+01:00
msg: libmount: fix UID check for FUSE umount [CVE-2021-3995]

Improper UID check allows an unprivileged user to unmount FUSE
filesystems of users with similar UID.

Signed-off-by: Karel Zak <kzak@redhat.com>
repository: repo_url: https://github.com/util-linux/util-linux
repo_name: util-linux/util-linux
repo_language: C
owner: util-linux
file_changes: filename: context_umount.c
change_type: ModificationType.MODIFY
diff: @@ -452,10 +452,7 @@ static int is_fuse_usermount(struct libmnt_context *cxt, int *errsv)
 	struct libmnt_ns *ns_old;
 	const char *type = mnt_fs_get_fstype(cxt->fs);
 	const char *optstr;
-	char *user_id = NULL;
-	size_t sz;
-	uid_t uid;
-	char uidstr[sizeof(stringify_value(ULONG_MAX))];
+	uid_t uid, entry_uid;
 
 	*errsv = 0;
 
@@ -472,11 +469,7 @@ static int is_fuse_usermount(struct libmnt_context *cxt, int *errsv)
 	optstr = mnt_fs_get_fs_options(cxt->fs);
 	if (!optstr)
 		return 0;
-
-	if (mnt_optstr_get_option(optstr, "user_id", &user_id, &sz) != 0)
-		return 0;
-
-	if (sz == 0 || user_id == NULL)
+	if (mnt_optstr_get_uid(optstr, "user_id", &entry_uid) != 0)
 		return 0;
 
 	/* get current user */
@@ -493,8 +486,7 @@ static int is_fuse_usermount(struct libmnt_context *cxt, int *errsv)
 		return 0;
 	}
 
-	snprintf(uidstr, sizeof(uidstr), "%lu", (unsigned long) uid);
-	return strncmp(user_id, uidstr, sz) == 0;
+	return uid == entry_uid;
 }
 
 /*

programming_language: C
filename: mountP.h
change_type: ModificationType.MODIFY
diff: @@ -399,6 +399,7 @@ extern const struct libmnt_optmap *mnt_optmap_get_entry(
 			     const struct libmnt_optmap **mapent);
 
 /* optstr.c */
+extern int mnt_optstr_get_uid(const char *optstr, const char *name, uid_t *uid);
 extern int mnt_optstr_remove_option_at(char **optstr, char *begin, char *end);
 extern int mnt_optstr_fix_gid(char **optstr, char *value, size_t valsz, char **next);
 extern int mnt_optstr_fix_uid(char **optstr, char *value, size_t valsz, char **next);

programming_language: C
filename: optstr.c
change_type: ModificationType.MODIFY
diff: @@ -1083,6 +1083,48 @@ int mnt_optstr_fix_user(char **optstr)
 	return rc;
 }
 
+/*
+ * Converts value from @optstr addressed by @name to uid.
+ *
+ * Returns: 0 on success, 1 if not found, <0 on error
+ */
+int mnt_optstr_get_uid(const char *optstr, const char *name, uid_t *uid)
+{
+	char *value = NULL;
+	size_t valsz = 0;
+	char buf[sizeof(stringify_value(UINT64_MAX))];
+	int rc;
+	uint64_t num;
+
+	assert(optstr);
+	assert(name);
+	assert(uid);
+
+	rc = mnt_optstr_get_option(optstr, name, &value, &valsz);
+	if (rc != 0)
+		goto fail;
+
+	if (valsz > sizeof(buf) - 1) {
+		rc = -ERANGE;
+		goto fail;
+	}
+	mem2strcpy(buf, value, valsz, sizeof(buf));
+
+	rc = ul_strtou64(buf, &num, 10);
+	if (rc != 0)
+		goto fail;
+	if (num > ULONG_MAX || (uid_t) num != num) {
+		rc = -ERANGE;
+		goto fail;
+	}
+	*uid = (uid_t) num;
+
+	return 0;
+fail:
+	DBG(UTILS, ul_debug("failed to convert '%s'= to number [rc=%d]", name, rc));
+	return rc;
+}
+
 /**
  * mnt_match_options:
  * @optstr: options string

programming_language: C