CVE-2021-43395 (published 2022-12-26T06:15Z)

An issue was discovered in illumos before f859e7171bb5db34321e45585839c6c3200ebb90, OmniOS Community Edition r151038, OpenIndiana Hipster 2021.04, and SmartOS 20210923. A local unprivileged user can cause a deadlock and kernel panic via crafted rename and rmdir calls on tmpfs filesystems. Oracle Solaris 10 and 11 is also affected.

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-667
cwe_name: Improper Locking
description: The product does not properly acquire or release a lock on a resource, leading to unexpected resource state changes and behaviors.
extended_description: 
	   Locking is a type of synchronization behavior that ensures that multiple independently-operating processes or threads do not interfere with each other when accessing the same resource. All processes/threads are expected to follow the same steps for locking. If these steps are not followed precisely - or if no locking is done at all - then another process/thread could modify the shared resource in a way that is not visible or predictable to the original process.  This can lead to data or memory corruption, denial of service, etc.
         

Fix information:
repo_url: https://github.com/illumos/illumos-gate
commit_details: repo_url: https://github.com/illumos/illumos-gate
author: Dan McDonald
author_date: 2021-11-04 21:55:12-04:00
msg: 14424 tmpfs can be induced to deadlock
Reviewed by: Robert Mustacchi <rm@fingolfin.org>
Reviewed by: Andy Fiddaman <andy@omnios.org>
Reviewed by: Mike Zeller <mike.zeller@joyent.com>
Approved by: Robert Mustacchi <rm@fingolfin.org>
repository: repo_url: https://github.com/illumos/illumos-gate
repo_name: illumos/illumos-gate
description: An open-source Unix operating system
repo_language: C
owner: illumos
file_changes: filename: tmp_dir.c
change_type: ModificationType.MODIFY
diff: @@ -55,6 +55,11 @@ static int tdiraddentry(struct tmpnode *, struct tmpnode *, char *,
 #define	T_HASH_SIZE	8192		/* must be power of 2 */
 #define	T_MUTEX_SIZE	64
 
+/* Non-static so compilers won't constant-fold these away. */
+clock_t tmpfs_rename_backoff_delay = 1;
+unsigned int tmpfs_rename_backoff_tries = 0;
+unsigned long tmpfs_rename_loops = 0;
+
 static struct tdirent	*t_hashtable[T_HASH_SIZE];
 static kmutex_t		 t_hashmutex[T_MUTEX_SIZE];
 
@@ -267,8 +272,65 @@ tdirenter(
 	 * to see if it has been removed while it was unlocked.
 	 */
 	if (op == DE_LINK || op == DE_RENAME) {
-		if (tp != dir)
-			rw_enter(&tp->tn_rwlock, RW_WRITER);
+		if (tp != dir) {
+			unsigned int tries = 0;
+
+			/*
+			 * If we are acquiring tp->tn_rwlock (for SOURCE)
+			 * inside here, we must consider the following:
+			 *
+			 * - dir->tn_rwlock (TARGET) is already HELD (see
+			 * above ASSERT()).
+			 *
+			 * - It is possible our SOURCE is a parent of our
+			 * TARGET. Yes it's unusual, but it will return an
+			 * error below via tdircheckpath().
+			 *
+			 * - It is also possible that another thread,
+			 * concurrent to this one, is performing
+			 * rmdir(TARGET), which means it will first acquire
+			 * SOURCE's lock, THEN acquire TARGET's lock, which
+			 * could result in this thread holding TARGET and
+			 * trying for SOURCE, but the other thread holding
+			 * SOURCE and trying for TARGET.  This is deadlock,
+			 * and it's inducible.
+			 *
+			 * To prevent this, we borrow some techniques from UFS
+			 * and rw_tryenter(), delaying if we fail, and
+			 * if someone tweaks the number of backoff tries to be
+			 * nonzero, return EBUSY after that number of tries.
+			 */
+			while (!rw_tryenter(&tp->tn_rwlock, RW_WRITER)) {
+				/*
+				 * Sloppy, but this is a diagnostic so atomic
+				 * increment would be overkill.
+				 */
+				tmpfs_rename_loops++;
+
+				if (tmpfs_rename_backoff_tries != 0) {
+					if (tries > tmpfs_rename_backoff_tries)
+						return (EBUSY);
+					tries++;
+				}
+				/*
+				 * NOTE: We're still holding dir->tn_rwlock,
+				 * so drop it over the delay, so any other
+				 * thread can get its business done.
+				 *
+				 * No state change or state inspection happens
+				 * prior to here, so it is not wholly dangerous
+				 * to release-and-reacquire dir->tn_rwlock.
+				 *
+				 * Hold the vnode of dir in case it gets
+				 * released by another thread, though.
+				 */
+				VN_HOLD(TNTOV(dir));
+				rw_exit(&dir->tn_rwlock);
+				delay(tmpfs_rename_backoff_delay);
+				rw_enter(&dir->tn_rwlock, RW_WRITER);
+				VN_RELE(TNTOV(dir));
+			}
+		}
 		mutex_enter(&tp->tn_tlock);
 		if (tp->tn_nlink == 0) {
 			mutex_exit(&tp->tn_tlock);

programming_language: C