CVE-2021-44512: Incorrect Permission Assignment for Critical Resource (CWE-732)
value: World-writable permissions on the /tmp/tmate/sessions directory in tmate-ssh-server 2.3.0 allow a local attacker to compromise the integrity of session handling, or obtain the read-write session ID from a read-only session symlink in this directory.
CWE description: The product specifies permissions for a security-critical resource in a way that allows that resource to be read or modified by unintended actors.
CVSS v3 base score: 7
Published: 2021-12-07T03:15Z
Repository: https://github.com/tmate-io/tmate-ssh-server
Language: C
Files changed: tmate-main.c
Commit date: 2021-10-17 17:06:49 -0400
Commit message: Harden /tmp/tmate directory

Suggested by Matthias Gerstner

Diff: diff --git a/tmate-main.c b/tmate-main.c
index 86560ecb..69f33209 100644
--- a/tmate-main.c
+++ b/tmate-main.c
@@ -96,10 +96,28 @@ static void setup_locale(void)
 
 	setlocale(LC_TIME, "");
 	tzset();
 }
 
+static int check_owned_directory_mode(const char *path, mode_t expected_mode)
+{
+	struct stat stat;
+	if (lstat(path, &stat))
+		return -1;
+
+	if (!S_ISDIR(stat.st_mode))
+		return -1;
+
+	if (stat.st_uid != getuid())
+		return -1;
+
+	if ((stat.st_mode & 07777) != expected_mode)
+		return -1;
+
+	return 0;
+}
+
 int main(int argc, char **argv, char **envp)
 {
 	int opt;
 
 	while ((opt = getopt(argc, argv, "b:h:k:p:q:w:z:xv")) != -1) {
@@ -149,21 +167,26 @@ int main(int argc, char **argv, char **envp)
 
 	tmate_preload_trace_lib();
 	tmate_catch_sigsegv();
 	tmate_init_rand();
 
-	if ((mkdir(TMATE_WORKDIR, 0701)             < 0 && errno != EEXIST) ||
-	    (mkdir(TMATE_WORKDIR "/sessions", 0703) < 0 && errno != EEXIST) ||
+	if ((mkdir(TMATE_WORKDIR, 0700)             < 0 && errno != EEXIST) ||
+	    (mkdir(TMATE_WORKDIR "/sessions", 0700) < 0 && errno != EEXIST) ||
 	    (mkdir(TMATE_WORKDIR "/jail", 0700)     < 0 && errno != EEXIST))
 		tmate_fatal("Cannot prepare session in " TMATE_WORKDIR);
 
-	/* The websocket server needs to access the /session dir to rename sockets */
-	if ((chmod(TMATE_WORKDIR, 0701)             < 0) ||
-	    (chmod(TMATE_WORKDIR "/sessions", 0703) < 0) ||
+	if ((chmod(TMATE_WORKDIR, 0700)             < 0) ||
+	    (chmod(TMATE_WORKDIR "/sessions", 0700) < 0) ||
 	    (chmod(TMATE_WORKDIR "/jail", 0700)     < 0))
 		tmate_fatal("Cannot prepare session in " TMATE_WORKDIR);
 
+	if (check_owned_directory_mode(TMATE_WORKDIR, 0700) ||
+	    check_owned_directory_mode(TMATE_WORKDIR "/sessions", 0700) ||
+	    check_owned_directory_mode(TMATE_WORKDIR "/jail", 0700))
+		tmate_fatal(TMATE_WORKDIR " and subdirectories has incorrect ownership/mode. "
+			    "Try deleting " TMATE_WORKDIR " and try again");
+
 	tmate_ssh_server_main(tmate_session,
 			      tmate_settings->keys_dir, tmate_settings->bind_addr, tmate_settings->ssh_port);
 	return 0;
 }
 

Vulnerable code: 	if ((mkdir(TMATE_WORKDIR, 0701)             < 0 && errno != EEXIST) ||
	    (mkdir(TMATE_WORKDIR "/sessions", 0703) < 0 && errno != EEXIST) ||
	/* The websocket server needs to access the /session dir to rename sockets */
	if ((chmod(TMATE_WORKDIR, 0701)             < 0) ||
	    (chmod(TMATE_WORKDIR "/sessions", 0703) < 0) ||
Fixed code: static int check_owned_directory_mode(const char *path, mode_t expected_mode)
{
	struct stat stat;
	if (lstat(path, &stat))
		return -1;

	if (!S_ISDIR(stat.st_mode))
		return -1;

	if (stat.st_uid != getuid())
		return -1;

	if ((stat.st_mode & 07777) != expected_mode)
		return -1;

	return 0;
}

	if ((mkdir(TMATE_WORKDIR, 0700)             < 0 && errno != EEXIST) ||
	    (mkdir(TMATE_WORKDIR "/sessions", 0700) < 0 && errno != EEXIST) ||
	if ((chmod(TMATE_WORKDIR, 0700)             < 0) ||
	    (chmod(TMATE_WORKDIR "/sessions", 0700) < 0) ||
	if (check_owned_directory_mode(TMATE_WORKDIR, 0700) ||
	    check_owned_directory_mode(TMATE_WORKDIR "/sessions", 0700) ||
	    check_owned_directory_mode(TMATE_WORKDIR "/jail", 0700))
		tmate_fatal(TMATE_WORKDIR " and subdirectories has incorrect ownership/mode. "
			    "Try deleting " TMATE_WORKDIR " and try again");