From ebcd6eee3cd04babd9e8f5efbe2377317c95bcc8 Mon Sep 17 00:00:00 2001
From: Norbert Pocs <norbertpocs0@gmail.com>
Date: Wed, 27 Dec 2023 20:32:18 +0100
Subject: [PATCH] misc: Add function to check username syntax

Malicious code can be injected using the username with metacharacters,
therefore the username must be validated before using it with any %u.

Signed-off-by: Norbert Pocs <norbertpocs0@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
---
 include/libssh/misc.h          |  1 +
 src/misc.c                     | 32 ++++++++++++++++++++++++++++++++
 tests/unittests/torture_misc.c | 34 ++++++++++++++++++++++++++++++++++
 3 files changed, 67 insertions(+)

Index: libssh-0.10.6/include/libssh/misc.h
===================================================================
--- libssh-0.10.6.orig/include/libssh/misc.h
+++ libssh-0.10.6/include/libssh/misc.h
@@ -107,6 +107,7 @@ int ssh_tmpname(char *name);
 char *ssh_strreplace(const char *src, const char *pattern, const char *repl);
 
 int ssh_check_hostname_syntax(const char *hostname);
+int ssh_check_username_syntax(const char *username);
 
 FILE *ssh_strict_fopen(const char *filename, size_t max_file_size);
 
Index: libssh-0.10.6/src/misc.c
===================================================================
--- libssh-0.10.6.orig/src/misc.c
+++ libssh-0.10.6/src/misc.c
@@ -183,6 +183,7 @@ char *ssh_get_local_username(void)
 {
     DWORD size = 0;
     char *user;
+    int rc;
 
     /* get the size */
     GetUserName(NULL, &size);
@@ -193,7 +194,10 @@ char *ssh_get_local_username(void)
     }
 
     if (GetUserName(user, &size)) {
-        return user;
+        rc = ssh_check_username_syntax(user);
+        if (rc == SSH_OK) {
+            return user;
+        }
     }
 
     return NULL;
@@ -337,8 +341,10 @@ char *ssh_get_local_username(void)
     }
 
     name = strdup(pwd.pw_name);
+    rc = ssh_check_username_syntax(name);
 
-    if (name == NULL) {
+    if (rc != SSH_OK) {
+        free(name);
         return NULL;
     }
 
@@ -2148,4 +2154,36 @@ FILE *ssh_strict_fopen(const char *filen
     return f;
 }
 
+/**
+ * @brief Checks syntax of a username
+ *
+ * This check disallows metacharacters in the username
+ *
+ * @param username The username to be checked, has to be null terminated
+ *
+ * @return SSH_OK if the username passes syntax check
+ *         SSH_ERROR otherwise or if username is NULL or empty string
+ */
+int ssh_check_username_syntax(const char *username)
+{
+    size_t username_len;
+
+    if (username == NULL || *username == '-') {
+        return SSH_ERROR;
+    }
+
+    username_len = strlen(username);
+    if (username_len == 0 || username[username_len - 1] == '\\' ||
+        strpbrk(username, "'`\";&<>|(){}") != NULL) {
+        return SSH_ERROR;
+    }
+    for (size_t i = 0; i < username_len; i++) {
+        if (isspace(username[i]) != 0 && username[i + 1] == '-') {
+            return SSH_ERROR;
+        }
+    }
+
+    return SSH_OK;
+}
+
 /** @} */
Index: libssh-0.10.6/tests/unittests/torture_misc.c
===================================================================
--- libssh-0.10.6.orig/tests/unittests/torture_misc.c
+++ libssh-0.10.6/tests/unittests/torture_misc.c
@@ -843,6 +843,39 @@ static void torture_ssh_check_hostname_s
     assert_int_equal(rc, SSH_ERROR);
 }
 
+static void torture_ssh_check_username_syntax(void **state) {
+    int rc;
+    (void)state;
+
+    rc = ssh_check_username_syntax("username");
+    assert_int_equal(rc, SSH_OK);
+    rc = ssh_check_username_syntax("Alice");
+    assert_int_equal(rc, SSH_OK);
+    rc = ssh_check_username_syntax("Alice and Bob");
+    assert_int_equal(rc, SSH_OK);
+    rc = ssh_check_username_syntax("n4me?");
+    assert_int_equal(rc, SSH_OK);
+
+    rc = ssh_check_username_syntax("alice&bob");
+    assert_int_equal(rc, SSH_ERROR);
+    rc = ssh_check_username_syntax("backslash\\");
+    assert_int_equal(rc, SSH_ERROR);
+    rc = ssh_check_username_syntax("&var|()us\"<ha`r{}'");
+    assert_int_equal(rc, SSH_ERROR);
+    rc = ssh_check_username_syntax(" -");
+    assert_int_equal(rc, SSH_ERROR);
+    rc = ssh_check_username_syntax("me and -");
+    assert_int_equal(rc, SSH_ERROR);
+    rc = ssh_check_username_syntax("los -santos");
+    assert_int_equal(rc, SSH_ERROR);
+    rc = ssh_check_username_syntax("- who?");
+    assert_int_equal(rc, SSH_ERROR);
+    rc = ssh_check_username_syntax(NULL);
+    assert_int_equal(rc, SSH_ERROR);
+    rc = ssh_check_username_syntax("");
+    assert_int_equal(rc, SSH_ERROR);
+}
+
 static void torture_ssh_is_ipaddr(void **state) {
     int rc;
     char *interf = malloc(64);
@@ -936,6 +969,7 @@ int torture_run_tests(void) {
         cmocka_unit_test(torture_ssh_strreplace),
         cmocka_unit_test(torture_ssh_strerror),
         cmocka_unit_test(torture_ssh_check_hostname_syntax),
+        cmocka_unit_test(torture_ssh_check_username_syntax),
         cmocka_unit_test(torture_ssh_is_ipaddr),
         cmocka_unit_test(torture_ssh_get_hexa),
     };
Index: libssh-0.10.6/src/config_parser.c
===================================================================
--- libssh-0.10.6.orig/src/config_parser.c
+++ libssh-0.10.6/src/config_parser.c
@@ -197,6 +197,10 @@ int ssh_config_parse_uri(const char *tok
             if (*username == NULL) {
                 goto error;
             }
+            rc = ssh_check_username_syntax(*username);
+            if (rc != SSH_OK) {
+                goto error;
+            }
         }
         tok = endp + 1;
         /* If there is second @ character, this does not look like our URI */
Index: libssh-0.10.6/src/options.c
===================================================================
--- libssh-0.10.6.orig/src/options.c
+++ libssh-0.10.6/src/options.c
@@ -620,6 +620,11 @@ int ssh_options_set(ssh_session session,
                     ssh_set_error_oom(session);
                     return -1;
                 }
+                rc = ssh_check_username_syntax(session->opts.username);
+                if (rc != SSH_OK) {
+                    ssh_set_error_invalid(session);
+                    return -1;
+                }
             }
             break;
         case SSH_OPTIONS_SSH_DIR:
