From d7bb9fad67bab9cb30ea5fa399275770a8b0b4d8 Mon Sep 17 00:00:00 2001
From: Yadan Fan <ydfan@suse.com>
Date: Wed, 26 May 2021 09:59:01 +0200
Subject: [PATCH] add strict-scp-check for CVE-2020-15778

backported from src-openEuler/openssh
---
 ...-strict-scp-check-for-CVE-2020-15778.patch | 157 ++++++++++++++++++
 servconf.c                                    |  10 ++
 servconf.h                                    |   1 +
 session.c                                     |  50 ++++++
 4 files changed, 218 insertions(+)
 create mode 100644 0001-add-strict-scp-check-for-CVE-2020-15778.patch

diff --git a/0001-add-strict-scp-check-for-CVE-2020-15778.patch b/0001-add-strict-scp-check-for-CVE-2020-15778.patch
new file mode 100644
index 0000000..b61579a
--- /dev/null
+++ b/0001-add-strict-scp-check-for-CVE-2020-15778.patch
@@ -0,0 +1,157 @@
+From 07de6e9054af2c57831655551abc5a9b36b01e6d Mon Sep 17 00:00:00 2001
+From: Yadan Fan <ydfan@suse.com>
+Date: Wed, 26 May 2021 09:59:01 +0200
+Subject: [PATCH] add strict-scp-check for CVE-2020-15778
+
+backported from src-openEuler/openssh
+---
+ servconf.c | 10 ++++++++++
+ servconf.h |  1 +
+ session.c  | 49 +++++++++++++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 60 insertions(+)
+
+diff --git a/servconf.c b/servconf.c
+index b3b0953..42f423c 100644
+--- a/servconf.c
++++ b/servconf.c
+@@ -69,6 +69,7 @@ initialize_server_options(ServerOptions *options)
+ {
+ 	memset(options, 0, sizeof(*options));
+ 
++	options->strict_scp_check = -1;
+ 	/* Portable-specific options */
+ 	options->use_pam = -1;
+ 	options->use_pam_check_locks = -1;
+@@ -193,6 +194,8 @@ fill_default_server_options(ServerOptions *options)
+ #endif
+ 		}
+ 	}
++	if (options->strict_scp_check == -1)
++		options->strict_scp_check = 0;
+ 	/* No certificates by default */
+ 	if (options->num_ports == 0)
+ 		options->ports[options->num_ports++] = SSH_DEFAULT_PORT;
+@@ -332,6 +335,7 @@ fill_default_server_options(ServerOptions *options)
+ /* Keyword tokens. */
+ typedef enum {
+ 	sBadOption,		/* == unknown option */
++	sStrictScpCheck,
+ 	/* Portable-specific options */
+ 	sUsePAM, sUsePAMChecklocks,
+ 	/* Standard Options */
+@@ -385,6 +389,7 @@ static struct {
+ 	{ "usepam", sUnsupported, SSHCFG_GLOBAL },
+ 	{ "usepamchecklocks", sUnsupported, SSHCFG_GLOBAL },
+ #endif
++	{ "strictscpcheck", sStrictScpCheck, SSHCFG_GLOBAL },
+ 	{ "pamauthenticationviakbdint", sDeprecated, SSHCFG_GLOBAL },
+ 	/* Standard Options */
+ 	{ "port", sPort, SSHCFG_GLOBAL },
+@@ -895,6 +900,9 @@ process_server_config_line(ServerOptions *options, char *line,
+ 	/* Standard Options */
+ 	case sBadOption:
+ 		return -1;
++	case sStrictScpCheck:
++		intptr = &options->strict_scp_check;
++		goto parse_flag;
+ 	case sPort:
+ 		/* ignore ports from configfile if cmdline specifies ports */
+ 		if (options->ports_from_cmdline)
+@@ -1752,6 +1760,7 @@ copy_set_server_options(ServerOptions *dst, ServerOptions *src, int preauth)
+                dst->n = src->n; \
+ } while (0)
+ 
++	M_CP_INTOPT(strict_scp_check);
+ 	M_CP_INTOPT(password_authentication);
+ 	M_CP_INTOPT(gss_authentication);
+ 	M_CP_INTOPT(rsa_authentication);
+@@ -1980,6 +1989,7 @@ dump_config(ServerOptions *o)
+ 	dump_cfg_int(sKexDHMin, o->kex_dhmin);
+ 
+ 	/* formatted integer arguments */
++	dump_cfg_fmtint(sStrictScpCheck, o->strict_scp_check);
+ 	dump_cfg_fmtint(sPermitRootLogin, o->permit_root_login);
+ 	dump_cfg_fmtint(sIgnoreRhosts, o->ignore_rhosts);
+ 	dump_cfg_fmtint(sIgnoreUserKnownHosts, o->ignore_user_known_hosts);
+diff --git a/servconf.h b/servconf.h
+index 7a855d0..7fa0641 100644
+--- a/servconf.h
++++ b/servconf.h
+@@ -163,6 +163,7 @@ typedef struct {
+ 					 * disconnect the session
+ 					 */
+ 
++	int	strict_scp_check;
+ 	u_int num_authkeys_files;	/* Files containing public keys */
+ 	char   *authorized_keys_files[MAX_AUTHKEYS_FILES];
+ 
+diff --git a/session.c b/session.c
+index 2345d11..4a2930a 100644
+--- a/session.c
++++ b/session.c
+@@ -165,6 +165,50 @@ static char *auth_sock_dir = NULL;
+ 
+ /* removes the agent forwarding socket */
+ 
++int scp_check(const char *command)
++{
++	debug("Entering scp check");
++        int check = 0;
++        if (command == NULL) {
++		debug("scp check succeeded for shell mode");
++                return check;
++        }
++        int lc = strlen(command);
++        char special_characters[] = "|;&$><`\\!\n";
++        int ls = strlen(special_characters);
++        int count_char[128] = {0};
++
++        for (int i = 0; i < ls; i++) {
++                count_char[special_characters[i]] = 1;
++        }
++
++        char scp_prefix[6] = "scp -";
++        int lp = 5;
++
++        if (lc <= lp) {
++		debug("scp check succeeded for length");
++                return check;
++        }
++
++        for (int i = 0; i < lp; i++) {
++                if (command[i] - scp_prefix[i]) {
++			debug("scp check succeeded for prefix");
++                        return check;
++                }
++        }
++
++        for (int i = lp; i < lc; i++) {
++                if (command[i] > 0 && command[i] < 128) {
++                        if (count_char[command[i]]) {
++                                check = 1;
++				debug("scp check failed at %d: %c", i, command[i]);
++                                break;
++                        }
++                }
++        }
++        return check;
++}
++
+ static void
+ auth_sock_cleanup_proc(struct passwd *pw)
+ {
+@@ -845,6 +889,11 @@ do_exec(Session *s, const char *command)
+ 		debug("Forced command (key option) '%.900s'", command);
+ 	}
+ 
++	if (options.strict_scp_check && scp_check(command)) {
++       		 verbose("Special characters not allowed in scp");
++        	 return 1;
++    	}
++
+ #ifdef SSH_AUDIT_EVENTS
+ 	if (s->command != NULL || s->command_handle != -1)
+ 		fatal("do_exec: command already set");
+-- 
+2.26.2
+
diff --git a/servconf.c b/servconf.c
index b3b0953..42f423c 100644
--- a/servconf.c
+++ b/servconf.c
@@ -69,6 +69,7 @@ initialize_server_options(ServerOptions *options)
 {
 	memset(options, 0, sizeof(*options));
 
+	options->strict_scp_check = -1;
 	/* Portable-specific options */
 	options->use_pam = -1;
 	options->use_pam_check_locks = -1;
@@ -193,6 +194,8 @@ fill_default_server_options(ServerOptions *options)
 #endif
 		}
 	}
+	if (options->strict_scp_check == -1)
+		options->strict_scp_check = 0;
 	/* No certificates by default */
 	if (options->num_ports == 0)
 		options->ports[options->num_ports++] = SSH_DEFAULT_PORT;
@@ -332,6 +335,7 @@ fill_default_server_options(ServerOptions *options)
 /* Keyword tokens. */
 typedef enum {
 	sBadOption,		/* == unknown option */
+	sStrictScpCheck,
 	/* Portable-specific options */
 	sUsePAM, sUsePAMChecklocks,
 	/* Standard Options */
@@ -385,6 +389,7 @@ static struct {
 	{ "usepam", sUnsupported, SSHCFG_GLOBAL },
 	{ "usepamchecklocks", sUnsupported, SSHCFG_GLOBAL },
 #endif
+	{ "strictscpcheck", sStrictScpCheck, SSHCFG_GLOBAL },
 	{ "pamauthenticationviakbdint", sDeprecated, SSHCFG_GLOBAL },
 	/* Standard Options */
 	{ "port", sPort, SSHCFG_GLOBAL },
@@ -895,6 +900,9 @@ process_server_config_line(ServerOptions *options, char *line,
 	/* Standard Options */
 	case sBadOption:
 		return -1;
+	case sStrictScpCheck:
+		intptr = &options->strict_scp_check;
+		goto parse_flag;
 	case sPort:
 		/* ignore ports from configfile if cmdline specifies ports */
 		if (options->ports_from_cmdline)
@@ -1752,6 +1760,7 @@ copy_set_server_options(ServerOptions *dst, ServerOptions *src, int preauth)
                dst->n = src->n; \
 } while (0)
 
+	M_CP_INTOPT(strict_scp_check);
 	M_CP_INTOPT(password_authentication);
 	M_CP_INTOPT(gss_authentication);
 	M_CP_INTOPT(rsa_authentication);
@@ -1980,6 +1989,7 @@ dump_config(ServerOptions *o)
 	dump_cfg_int(sKexDHMin, o->kex_dhmin);
 
 	/* formatted integer arguments */
+	dump_cfg_fmtint(sStrictScpCheck, o->strict_scp_check);
 	dump_cfg_fmtint(sPermitRootLogin, o->permit_root_login);
 	dump_cfg_fmtint(sIgnoreRhosts, o->ignore_rhosts);
 	dump_cfg_fmtint(sIgnoreUserKnownHosts, o->ignore_user_known_hosts);
diff --git a/servconf.h b/servconf.h
index 7a855d0..7fa0641 100644
--- a/servconf.h
+++ b/servconf.h
@@ -163,6 +163,7 @@ typedef struct {
 					 * disconnect the session
 					 */
 
+	int	strict_scp_check;
 	u_int num_authkeys_files;	/* Files containing public keys */
 	char   *authorized_keys_files[MAX_AUTHKEYS_FILES];
 
diff --git a/session.c b/session.c
index 2345d11..905014f 100644
--- a/session.c
+++ b/session.c
@@ -165,6 +165,51 @@ static char *auth_sock_dir = NULL;
 
 /* removes the agent forwarding socket */
 
+int scp_check(const char *command)
+{
+	debug("Entering scp check");
+        int check = 0;
+        if (command == NULL) {
+		debug("scp check succeeded for shell mode");
+                return check;
+        }
+        int lc = strlen(command);
+        char special_characters[] = "|;&$><`\\!\n";
+        int ls = strlen(special_characters);
+        int count_char[128] = {0};
+	int i;
+
+        for (i = 0; i < ls; i++) {
+                count_char[special_characters[i]] = 1;
+        }
+
+        char scp_prefix[6] = "scp -";
+        int lp = 5;
+
+        if (lc <= lp) {
+		debug("scp check succeeded for length");
+                return check;
+        }
+
+        for (i = 0; i < lp; i++) {
+                if (command[i] != scp_prefix[i]) {
+			debug("scp check succeeded for prefix");
+                        return check;
+                }
+        }
+
+        for (i = lp; i < lc; i++) {
+                if (command[i] > 0 && command[i] < 128) {
+                        if (count_char[command[i]]) {
+                                check = 1;
+				debug("scp check failed at %d: %c", i, command[i]);
+                                break;
+                        }
+                }
+        }
+        return check;
+}
+
 static void
 auth_sock_cleanup_proc(struct passwd *pw)
 {
@@ -845,6 +890,11 @@ do_exec(Session *s, const char *command)
 		debug("Forced command (key option) '%.900s'", command);
 	}
 
+	if (options.strict_scp_check && scp_check(command)) {
+       		 verbose("Special characters not allowed in scp");
+        	 return 1;
+    	}
+
 #ifdef SSH_AUDIT_EVENTS
 	if (s->command != NULL || s->command_handle != -1)
 		fatal("do_exec: command already set");
-- 
2.26.2

