|
| 1 | +From dd02e9decdb3d0a171c71666793afb8d36de2292 Mon Sep 17 00:00:00 2001 |
| 2 | +From: AllSpark < [email protected]> |
| 3 | +Date: Thu, 9 Oct 2025 16:32:16 +0000 |
| 4 | +Subject: [PATCH] backport: Improve rules for %-expansion of username; avoid |
| 5 | + expanding commandline user, add control-char check in valid_ruser, validate |
| 6 | + expanded/literal users accordingly |
| 7 | + |
| 8 | +Signed-off-by: Azure Linux Security Servicing Account < [email protected]> |
| 9 | +Upstream-reference: AI Backport of https://github.com/openssh/openssh-portable/commit/43b3bff47bb029f2299bacb6a36057981b39fdb0.patch |
| 10 | +--- |
| 11 | + ssh.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- |
| 12 | + 1 file changed, 58 insertions(+), 3 deletions(-) |
| 13 | + |
| 14 | +diff --git a/ssh.c b/ssh.c |
| 15 | +index 0019281..e871aa3 100644 |
| 16 | +--- a/ssh.c |
| 17 | ++++ b/ssh.c |
| 18 | +@@ -243,6 +243,31 @@ default_client_percent_dollar_expand(const char *str, |
| 19 | + return ret; |
| 20 | + } |
| 21 | + |
| 22 | ++/* Like default_client_percent_dollar_expand() but exclude %r and %C */ |
| 23 | ++static char * |
| 24 | ++default_client_percent_dollar_expand_nouser(const char *str, |
| 25 | ++ const struct ssh_conn_info *cinfo) |
| 26 | ++{ |
| 27 | ++ char *ret; |
| 28 | ++ |
| 29 | ++ ret = percent_dollar_expand(str, |
| 30 | ++ /* omit C (conn_hash_hex) and r (remuser) */ |
| 31 | ++ "L", cinfo->shorthost, |
| 32 | ++ "i", cinfo->uidstr, |
| 33 | ++ "k", cinfo->keyalias, |
| 34 | ++ "l", cinfo->thishost, |
| 35 | ++ "n", cinfo->host_arg, |
| 36 | ++ "p", cinfo->portstr, |
| 37 | ++ "d", cinfo->homedir, |
| 38 | ++ "h", cinfo->remhost, |
| 39 | ++ "u", cinfo->locuser, |
| 40 | ++ "j", cinfo->jmphost, |
| 41 | ++ (char *)NULL); |
| 42 | ++ if (ret == NULL) |
| 43 | ++ fatal("invalid environment variable expansion"); |
| 44 | ++ return ret; |
| 45 | ++} |
| 46 | ++ |
| 47 | + /* |
| 48 | + * Attempt to resolve a host name / port to a set of addresses and |
| 49 | + * optionally return any CNAMEs encountered along the way. |
| 50 | +@@ -670,6 +695,7 @@ main(int ac, char **av) |
| 51 | + struct ssh *ssh = NULL; |
| 52 | + int i, r, opt, exit_status, use_syslog, direct, timeout_ms; |
| 53 | + int was_addr, config_test = 0, opt_terminated = 0, want_final_pass = 0; |
| 54 | ++ int user_on_commandline = 0, user_was_default = 0, user_expanded = 0; |
| 55 | + char *p, *cp, *line, *argv0, *logfile; |
| 56 | + char cname[NI_MAXHOST], thishost[NI_MAXHOST]; |
| 57 | + struct stat st; |
| 58 | +@@ -1016,8 +1042,10 @@ main(int ac, char **av) |
| 59 | + } |
| 60 | + break; |
| 61 | + case 'l': |
| 62 | +- if (options.user == NULL) |
| 63 | ++ if (options.user == NULL) { |
| 64 | + options.user = optarg; |
| 65 | ++ user_on_commandline = 1; |
| 66 | ++ } |
| 67 | + break; |
| 68 | + |
| 69 | + case 'L': |
| 70 | +@@ -1288,8 +1316,10 @@ main(int ac, char **av) |
| 71 | + if (fill_default_options(&options) != 0) |
| 72 | + cleanup_exit(255); |
| 73 | + |
| 74 | +- if (options.user == NULL) |
| 75 | ++ if (options.user == NULL) { |
| 76 | ++ user_was_default = 1; |
| 77 | + options.user = xstrdup(pw->pw_name); |
| 78 | ++ } |
| 79 | + |
| 80 | + /* |
| 81 | + * If ProxyJump option specified, then construct a ProxyCommand now. |
| 82 | +@@ -1430,11 +1460,36 @@ main(int ac, char **av) |
| 83 | + options.host_key_alias : options.host_arg); |
| 84 | + cinfo->host_arg = xstrdup(options.host_arg); |
| 85 | + cinfo->remhost = xstrdup(host); |
| 86 | +- cinfo->remuser = xstrdup(options.user); |
| 87 | + cinfo->homedir = xstrdup(pw->pw_dir); |
| 88 | + cinfo->locuser = xstrdup(pw->pw_name); |
| 89 | + cinfo->jmphost = xstrdup(options.jump_host == NULL ? |
| 90 | + "" : options.jump_host); |
| 91 | ++ |
| 92 | ++ /* |
| 93 | ++ * If the user was specified via a configuration directive then attempt |
| 94 | ++ * to expand it. It cannot contain %r (itself) or %C since User is |
| 95 | ++ * a component of the hash. |
| 96 | ++ */ |
| 97 | ++ if (!user_on_commandline && !user_was_default) { |
| 98 | ++ char *up; |
| 99 | ++ up = default_client_percent_dollar_expand_nouser(options.user, cinfo); |
| 100 | ++ user_expanded = strcmp(up, options.user) != 0; |
| 101 | ++ free(options.user); |
| 102 | ++ options.user = up; |
| 103 | ++ } |
| 104 | ++ |
| 105 | ++ /* |
| 106 | ++ * Usernames specified on the commandline or expanded from the |
| 107 | ++ * configuration file must be validated. |
| 108 | ++ * Conversely, usernames from getpwnam(3) or specified as literals |
| 109 | ++ * via configuration (i.e. not expanded) are not subject to validation. |
| 110 | ++ */ |
| 111 | ++ if ((user_on_commandline || user_expanded) && |
| 112 | ++ !valid_ruser(options.user)) |
| 113 | ++ fatal("remote username contains invalid characters"); |
| 114 | ++ |
| 115 | ++ /* Now User is expanded, store it and calculate hash. */ |
| 116 | ++ cinfo->remuser = xstrdup(options.user); |
| 117 | + cinfo->conn_hash_hex = ssh_connection_hash(cinfo->thishost, |
| 118 | + cinfo->remhost, cinfo->portstr, cinfo->remuser, cinfo->jmphost); |
| 119 | + |
| 120 | +-- |
| 121 | +2.45.4 |
| 122 | + |
0 commit comments