From 24c12611054b9b4bc075919cf735df857d401c4e Mon Sep 17 00:00:00 2001 From: Zoltan Fridrich Date: Mon, 27 Oct 2025 15:59:16 +0100 Subject: [PATCH 1/2] Fix implicit destination path selection when source path ends with ".." Resolves: RHEL-118406 Signed-off-by: Zoltan Fridrich --- openssh-9.9p1-scp-traversing.patch | 437 +++++++++++++++++++++++++++++ openssh.spec | 10 +- 2 files changed, 446 insertions(+), 1 deletion(-) create mode 100644 openssh-9.9p1-scp-traversing.patch diff --git a/openssh-9.9p1-scp-traversing.patch b/openssh-9.9p1-scp-traversing.patch new file mode 100644 index 0000000..2378b3a --- /dev/null +++ b/openssh-9.9p1-scp-traversing.patch @@ -0,0 +1,437 @@ +diff --color -ruNp a/regress/scp3.sh b/regress/scp3.sh +--- a/regress/scp3.sh 2024-09-20 00:20:48.000000000 +0200 ++++ b/regress/scp3.sh 2025-10-29 16:00:59.815068193 +0100 +@@ -6,6 +6,12 @@ tid="scp3" + COPY2=${OBJ}/copy2 + DIR=${COPY}.dd + DIR2=${COPY}.dd2 ++DIFFOPT="-rN" ++ ++# Figure out if diff does not understand "-N" ++if ! diff -N ${SRC}/scp.sh ${SRC}/scp.sh 2>/dev/null; then ++ DIFFOPT="-r" ++fi + + maybe_add_scp_path_to_sshd + +@@ -63,6 +69,15 @@ for mode in scp sftp ; do + echo b > ${COPY2} + $SCP $scpopts -3 hostA:${DATA} hostA:${COPY} hostB:${COPY2} + cmp ${COPY} ${COPY2} >/dev/null && fail "corrupt target" ++ ++ # scp /blah/.. is only supported via the sftp protocol. ++ # Original protocol scp just refuses it. ++ test $mode != sftp && continue ++ verbose "$tag: recursive .." ++ forest ++ $SCP $scpopts -r hostA:${DIR}/subdir/.. hostB:${DIR2} || \ ++ fail "copy failed" ++ diff ${DIFFOPT} ${DIR} ${DIR2} || fail "corrupted copy" + done + + scpclean +diff --color -ruNp a/regress/scp.sh b/regress/scp.sh +--- a/regress/scp.sh 2024-09-20 00:20:48.000000000 +0200 ++++ b/regress/scp.sh 2025-10-29 16:00:59.810765653 +0100 +@@ -199,6 +199,19 @@ for mode in scp sftp ; do + echo b > ${COPY2} + $SCP $scpopts ${DATA} ${COPY} ${COPY2} + cmp ${COPY} ${COPY2} >/dev/null && fail "corrupt target" ++ ++ # scp /blah/.. is only supported via the sftp protocol. ++ # Original protocol scp just refuses it. ++ test $mode != sftp && continue ++ verbose "$tag: recursive local .. to remote dir" ++ forest ++ $SCP $scpopts -r ${DIR}/subdir/.. somehost:${DIR2} || fail "copy failed" ++ diff ${DIFFOPT} ${DIR} ${DIR2} || fail "corrupted copy" ++ ++ verbose "$tag: recursive remote .. to local dir" ++ forest ++ $SCP $scpopts -r somehost:${DIR}/subdir/.. ${DIR2} || fail "copy failed" ++ diff ${DIFFOPT} ${DIR} ${DIR2} || fail "corrupted copy" + done + + scpclean +diff --color -ruNp a/regress/sftp-cmds.sh b/regress/sftp-cmds.sh +--- a/regress/sftp-cmds.sh 2024-09-20 00:20:48.000000000 +0200 ++++ b/regress/sftp-cmds.sh 2025-10-29 16:00:59.813392901 +0100 +@@ -7,6 +7,12 @@ + + tid="sftp commands" + ++DIFFOPT="-rN" ++# Figure out if diff does not understand "-N" ++if ! diff -N ${SRC}/sftp-cmds.sh ${SRC}/sftp-cmds.sh 2>/dev/null; then ++ DIFFOPT="-r" ++fi ++ + # test that these files are readable! + for i in `(cd /bin;echo l*)` + do +@@ -24,207 +30,246 @@ SPACECOPY_ARG="${COPY}\ this\ has\ space + # File with glob metacharacters + GLOBMETACOPY="${COPY} [metachar].txt" + ++sftpserver() { ++ ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 ++} ++ ++sftpserver_with_stdout() { ++ ${SFTP} -D ${SFTPSERVER} 2>&1 ++} ++ ++forest() { ++ rm -rf ${COPY}.dd/* ++ rm -rf ${COPY}.dd2 ++ mkdir -p ${COPY}.dd/a ${COPY}.dd/b ${COPY}.dd/c ${COPY}.dd/a/d ++ echo 'A' > ${COPY}.dd/a/A ++ echo 'B' > ${COPY}.dd/a/B ++ echo 'C' > ${COPY}.dd/a/C ++ echo 'D' > ${COPY}.dd/a/D ++} ++ + rm -rf ${COPY} ${COPY}.1 ${COPY}.2 ${COPY}.dd ${COPY}.dd2 + mkdir ${COPY}.dd + + verbose "$tid: lls" +-printf "lcd ${OBJ}\nlls\n" | ${SFTP} -D ${SFTPSERVER} 2>&1 | \ ++printf "lcd ${OBJ}\nlls\n" | sftpserver_with_stdout | \ + grep copy.dd >/dev/null || fail "lls failed" + + verbose "$tid: lls w/path" +-echo "lls ${OBJ}" | ${SFTP} -D ${SFTPSERVER} 2>&1 | \ ++echo "lls ${OBJ}" | sftpserver_with_stdout | \ + grep copy.dd >/dev/null || fail "lls w/path failed" + + verbose "$tid: ls" +-echo "ls ${OBJ}" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \ +- || fail "ls failed" ++echo "ls ${OBJ}" | sftpserver || fail "ls failed" + # XXX always successful + + verbose "$tid: shell" +-echo "!echo hi there" | ${SFTP} -D ${SFTPSERVER} 2>&1 | \ ++echo "!echo hi there" | sftpserver_with_stdout | \ + egrep '^hi there$' >/dev/null || fail "shell failed" + + verbose "$tid: pwd" +-echo "pwd" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \ +- || fail "pwd failed" ++echo "pwd" | sftpserver || fail "pwd failed" + # XXX always successful + + verbose "$tid: lpwd" +-echo "lpwd" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \ +- || fail "lpwd failed" ++echo "lpwd" | sftpserver || fail "lpwd failed" + # XXX always successful + + verbose "$tid: quit" +-echo "quit" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \ +- || fail "quit failed" ++echo "quit" | sftpserver || fail "quit failed" + # XXX always successful + + verbose "$tid: help" +-echo "help" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \ +- || fail "help failed" ++echo "help" | sftpserver || fail "help failed" + # XXX always successful + + rm -f ${COPY} + verbose "$tid: get" +-echo "get $DATA $COPY" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \ +- || fail "get failed" ++echo "get $DATA $COPY" | sftpserver || fail "get failed" + cmp $DATA ${COPY} || fail "corrupted copy after get" + + rm -f ${COPY} + verbose "$tid: get quoted" +-echo "get \"$DATA\" $COPY" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \ +- || fail "get failed" ++echo "get \"$DATA\" $COPY" | sftpserver || fail "get failed" + cmp $DATA ${COPY} || fail "corrupted copy after get" + + rm -f ${QUOTECOPY} + cp $DATA ${QUOTECOPY} + verbose "$tid: get filename with quotes" +-echo "get \"$QUOTECOPY_ARG\" ${COPY}" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \ +- || fail "get failed" ++echo "get \"$QUOTECOPY_ARG\" ${COPY}" | sftpserver || fail "get failed" + cmp ${COPY} ${QUOTECOPY} || fail "corrupted copy after get with quotes" + rm -f ${QUOTECOPY} ${COPY} + + rm -f "$SPACECOPY" ${COPY} + cp $DATA "$SPACECOPY" + verbose "$tid: get filename with spaces" +-echo "get ${SPACECOPY_ARG} ${COPY}" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \ +- || fail "get failed" ++echo "get ${SPACECOPY_ARG} ${COPY}" | sftpserver || fail "get failed" + cmp ${COPY} "$SPACECOPY" || fail "corrupted copy after get with spaces" + + rm -f "$GLOBMETACOPY" ${COPY} + cp $DATA "$GLOBMETACOPY" + verbose "$tid: get filename with glob metacharacters" +-echo "get \"${GLOBMETACOPY}\" ${COPY}" | \ +- ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 || fail "get failed" ++echo "get \"${GLOBMETACOPY}\" ${COPY}" | sftpserver || fail "get failed" + cmp ${COPY} "$GLOBMETACOPY" || \ + fail "corrupted copy after get with glob metacharacters" + +-rm -f ${COPY}.dd/* ++rm -rf ${COPY}.dd/* + verbose "$tid: get to directory" +-echo "get $DATA ${COPY}.dd" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \ +- || fail "get failed" ++echo "get $DATA ${COPY}.dd" | sftpserver || fail "get failed" + cmp $DATA ${COPY}.dd/${DATANAME} || fail "corrupted copy after get" + +-rm -f ${COPY}.dd/* ++rm -rf ${COPY}.dd/* + verbose "$tid: glob get to directory" +-echo "get /bin/l* ${COPY}.dd" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \ +- || fail "get failed" ++echo "get /bin/l* ${COPY}.dd" | sftpserver || fail "get failed" + for x in $GLOBFILES; do + cmp /bin/$x ${COPY}.dd/$x || fail "corrupted copy after get" + done + +-rm -f ${COPY}.dd/* ++rm -rf ${COPY}.dd/* + verbose "$tid: get to local dir" +-printf "lcd ${COPY}.dd\nget $DATA\n" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \ +- || fail "get failed" ++printf "lcd ${COPY}.dd\nget $DATA\n" | sftpserver || fail "get failed" + cmp $DATA ${COPY}.dd/${DATANAME} || fail "corrupted copy after get" + +-rm -f ${COPY}.dd/* ++rm -rf ${COPY}.dd/* + verbose "$tid: glob get to local dir" +-printf "lcd ${COPY}.dd\nget /bin/l*\n" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \ +- || fail "get failed" ++printf "lcd ${COPY}.dd\nget /bin/l*\n" | sftpserver || fail "get failed" + for x in $GLOBFILES; do + cmp /bin/$x ${COPY}.dd/$x || fail "corrupted copy after get" + done + ++forest ++verbose "$tid: get recursive absolute" ++echo "get -R ${COPY}.dd ${COPY}.dd2" | sftpserver || fail "get failed" ++diff ${DIFFOPT} ${COPY}.dd ${COPY}.dd2 || fail "corrupted copy" ++ ++forest ++verbose "$tid: get recursive relative src" ++printf "cd ${COPY}.dd\n get -R . ${COPY}.dd2\n" | sftpserver || \ ++ fail "get failed" ++diff ${DIFFOPT} ${COPY}.dd ${COPY}.dd2 || fail "corrupted copy" ++ ++forest ++verbose "$tid: get relative .." ++printf "cd ${COPY}.dd/b\n get -R .. ${COPY}.dd2\n" | sftpserver || \ ++ fail "get failed" ++diff ${DIFFOPT} ${COPY}.dd ${COPY}.dd2 || fail "corrupted copy" ++ ++forest ++mkdir ${COPY}.dd2 ++verbose "$tid: get recursive relative .." ++printf "cd ${COPY}.dd/b\n lcd ${COPY}.dd2\n get -R ..\n" | sftpserver || \ ++ fail "get failed" ++diff ${DIFFOPT} ${COPY}.dd ${COPY}.dd2 || fail "corrupted copy" ++ + rm -f ${COPY} + verbose "$tid: put" +-echo "put $DATA $COPY" | \ +- ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 || fail "put failed" ++echo "put $DATA $COPY" | sftpserver || fail "put failed" + cmp $DATA ${COPY} || fail "corrupted copy after put" + + rm -f ${QUOTECOPY} + verbose "$tid: put filename with quotes" +-echo "put $DATA \"$QUOTECOPY_ARG\"" | \ +- ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 || fail "put failed" ++echo "put $DATA \"$QUOTECOPY_ARG\"" | sftpserver || fail "put failed" + cmp $DATA ${QUOTECOPY} || fail "corrupted copy after put with quotes" + + rm -f "$SPACECOPY" + verbose "$tid: put filename with spaces" +-echo "put $DATA ${SPACECOPY_ARG}" | \ +- ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 || fail "put failed" ++echo "put $DATA ${SPACECOPY_ARG}" | sftpserver || fail "put failed" + cmp $DATA "$SPACECOPY" || fail "corrupted copy after put with spaces" + +-rm -f ${COPY}.dd/* ++rm -rf ${COPY}.dd/* + verbose "$tid: put to directory" +-echo "put $DATA ${COPY}.dd" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \ +- || fail "put failed" ++echo "put $DATA ${COPY}.dd" | sftpserver || fail "put failed" + cmp $DATA ${COPY}.dd/${DATANAME} || fail "corrupted copy after put" + +-rm -f ${COPY}.dd/* ++rm -rf ${COPY}.dd/* + verbose "$tid: glob put to directory" +-echo "put /bin/l? ${COPY}.dd" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \ +- || fail "put failed" ++echo "put /bin/l? ${COPY}.dd" | sftpserver || fail "put failed" + for x in $GLOBFILES; do + cmp /bin/$x ${COPY}.dd/$x || fail "corrupted copy after put" + done + +-rm -f ${COPY}.dd/* ++rm -rf ${COPY}.dd/* + verbose "$tid: put to local dir" +-printf "cd ${COPY}.dd\nput $DATA\n" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \ +- || fail "put failed" ++printf "cd ${COPY}.dd\nput $DATA\n" | sftpserver || fail "put failed" + cmp $DATA ${COPY}.dd/${DATANAME} || fail "corrupted copy after put" + +-rm -f ${COPY}.dd/* ++rm -rf ${COPY}.dd/* + verbose "$tid: glob put to local dir" +-printf "cd ${COPY}.dd\nput /bin/l*\n" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \ +- || fail "put failed" ++printf "cd ${COPY}.dd\nput /bin/l*\n" | sftpserver || fail "put failed" + for x in $GLOBFILES; do + cmp /bin/$x ${COPY}.dd/$x || fail "corrupted copy after put" + done + ++forest ++verbose "$tid: put recursive absolute" ++echo "put -R ${COPY}.dd ${COPY}.dd2" | sftpserver || fail "put failed" ++diff ${DIFFOPT} ${COPY}.dd ${COPY}.dd2 || fail "corrupted copy" ++ ++forest ++verbose "$tid: put recursive relative src" ++printf "lcd ${COPY}.dd\n put -R . ${COPY}.dd2\n" | sftpserver || \ ++ fail "put failed" ++diff ${DIFFOPT} ${COPY}.dd ${COPY}.dd2 || fail "corrupted copy" ++ ++forest ++verbose "$tid: put recursive .." ++printf "lcd ${COPY}.dd/b\n put -R .. ${COPY}.dd2\n" | sftpserver || \ ++ fail "put failed" ++diff ${DIFFOPT} ${COPY}.dd ${COPY}.dd2 || fail "corrupted copy" ++ ++forest ++mkdir ${COPY}.dd2 ++verbose "$tid: put recursive .. relative" ++printf "lcd ${COPY}.dd/b\n cd ${COPY}.dd2\n put -R ..\n" | sftpserver || \ ++ fail "put failed" ++diff ${DIFFOPT} ${COPY}.dd ${COPY}.dd2 || fail "corrupted copy" ++ + verbose "$tid: rename" +-echo "rename $COPY ${COPY}.1" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \ +- || fail "rename failed" ++echo "rename $COPY ${COPY}.1" | sftpserver || fail "rename failed" + test -f ${COPY}.1 || fail "missing file after rename" + cmp $DATA ${COPY}.1 >/dev/null 2>&1 || fail "corrupted copy after rename" + + verbose "$tid: rename directory" +-echo "rename ${COPY}.dd ${COPY}.dd2" | \ +- ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 || \ ++rm -rf ${COPY}.dd2 ++echo "rename ${COPY}.dd ${COPY}.dd2" | sftpserver || \ + fail "rename directory failed" + test -d ${COPY}.dd && fail "oldname exists after rename directory" + test -d ${COPY}.dd2 || fail "missing newname after rename directory" + + verbose "$tid: ln" +-echo "ln ${COPY}.1 ${COPY}.2" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 || fail "ln failed" ++echo "ln ${COPY}.1 ${COPY}.2" | sftpserver || fail "ln failed" + test -f ${COPY}.2 || fail "missing file after ln" + cmp ${COPY}.1 ${COPY}.2 || fail "created file is not equal after ln" + + verbose "$tid: ln -s" + rm -f ${COPY}.2 +-echo "ln -s ${COPY}.1 ${COPY}.2" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 || fail "ln -s failed" ++echo "ln -s ${COPY}.1 ${COPY}.2" | sftpserver || fail "ln -s failed" + test -h ${COPY}.2 || fail "missing file after ln -s" + + verbose "$tid: cp" + rm -f ${COPY}.2 +-echo "cp ${COPY}.1 ${COPY}.2" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 || fail "cp failed" ++echo "cp ${COPY}.1 ${COPY}.2" | sftpserver || fail "cp failed" + cmp ${COPY}.1 ${COPY}.2 || fail "created file is not equal after cp" + + verbose "$tid: mkdir" +-echo "mkdir ${COPY}.dd" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \ +- || fail "mkdir failed" ++echo "mkdir ${COPY}.dd" | sftpserver || fail "mkdir failed" + test -d ${COPY}.dd || fail "missing directory after mkdir" + + # XXX do more here + verbose "$tid: chdir" +-echo "chdir ${COPY}.dd" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \ +- || fail "chdir failed" ++echo "chdir ${COPY}.dd" | sftpserver || fail "chdir failed" + + verbose "$tid: rmdir" +-echo "rmdir ${COPY}.dd" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \ +- || fail "rmdir failed" ++echo "rmdir ${COPY}.dd" | sftpserver || fail "rmdir failed" + test -d ${COPY}.1 && fail "present directory after rmdir" + + verbose "$tid: lmkdir" +-echo "lmkdir ${COPY}.dd" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \ +- || fail "lmkdir failed" ++echo "lmkdir ${COPY}.dd" | sftpserver || fail "lmkdir failed" + test -d ${COPY}.dd || fail "missing directory after lmkdir" + + # XXX do more here + verbose "$tid: lchdir" +-echo "lchdir ${COPY}.dd" | ${SFTP} -D ${SFTPSERVER} >/dev/null 2>&1 \ +- || fail "lchdir failed" ++echo "lchdir ${COPY}.dd" | sftpserver || fail "lchdir failed" + + rm -rf ${COPY} ${COPY}.1 ${COPY}.2 ${COPY}.dd ${COPY}.dd2 + rm -rf ${QUOTECOPY} "$SPACECOPY" "$GLOBMETACOPY" +diff --color -ruNp a/scp.c b/scp.c +--- a/scp.c 2025-10-29 15:49:51.795813258 +0100 ++++ b/scp.c 2025-10-29 16:00:59.817393000 +0100 +@@ -1362,6 +1362,10 @@ source_sftp(int argc, char *src, char *t + if ((filename = basename(src)) == NULL) + fatal("basename \"%s\": %s", src, strerror(errno)); + ++ /* Special handling for source of '..' */ ++ if (strcmp(filename, "..") == 0) ++ filename = "."; /* Upload to dest, not dest/.. */ ++ + /* + * No need to glob here - the local shell already took care of + * the expansions +@@ -1635,6 +1639,10 @@ sink_sftp(int argc, char *dst, const cha + goto out; + } + ++ /* Special handling for destination of '..' */ ++ if (strcmp(filename, "..") == 0) ++ filename = "."; /* Download to dest, not dest/.. */ ++ + if (dst_is_dir) + abs_dst = sftp_path_append(dst, filename); + else +diff --color -ruNp a/sftp.c b/sftp.c +--- a/sftp.c 2025-10-29 15:49:51.796907436 +0100 ++++ b/sftp.c 2025-10-29 16:00:59.819803948 +0100 +@@ -687,6 +687,10 @@ process_get(struct sftp_conn *conn, cons + goto out; + } + ++ /* Special handling for dest of '..' */ ++ if (strcmp(filename, "..") == 0) ++ filename = "."; /* Download to dest, not dest/.. */ ++ + if (g.gl_matchc == 1 && dst) { + if (local_is_dir(dst)) { + abs_dst = sftp_path_append(dst, filename); +@@ -781,6 +785,9 @@ process_put(struct sftp_conn *conn, cons + err = -1; + goto out; + } ++ /* Special handling for source of '..' */ ++ if (strcmp(filename, "..") == 0) ++ filename = "."; /* Upload to dest, not dest/.. */ + + free(abs_dst); + abs_dst = NULL; diff --git a/openssh.spec b/openssh.spec index 25e2e77..3bf6a6c 100644 --- a/openssh.spec +++ b/openssh.spec @@ -43,7 +43,7 @@ Summary: An open source implementation of SSH protocol version 2 Name: openssh Version: %{openssh_ver} -Release: 14%{?dist} +Release: 15%{?dist} URL: http://www.openssh.com/portable.html Source0: ftp://ftp.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-%{version}.tar.gz Source1: ftp://ftp.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-%{version}.tar.gz.asc @@ -216,6 +216,9 @@ Patch1026: openssh-9.9p1-bad-hostkey.patch Patch1027: openssh-9.9p1-support-authentication-indicators-in-GSSAPI.patch # Patch1028: openssh-9.9p1-fips-gss.patch +#upstream 6432b9f6a216d0f5fb43df500e9bc30bebb3f58b +#upstream 4f14ca8633a2c8c0a1a19165663421f0ab32f6ab +Patch1029: openssh-9.9p1-scp-traversing.patch License: BSD-3-Clause AND BSD-2-Clause AND ISC AND SSH-OpenSSH AND ssh-keyscan AND sprintf AND LicenseRef-Fedora-Public-Domain AND X11-distribute-modifications-variant Requires: /sbin/nologin @@ -409,6 +412,7 @@ gpgv2 --quiet --keyring %{SOURCE3} %{SOURCE1} %{SOURCE0} %patch -P 1026 -p1 -b .bad-hostkey %patch -P 1027 -p1 -b .gss-indicators %patch -P 1028 -p1 -b .gss-fips +%patch -P 1029 -p1 -b .scp-traversing %patch -P 100 -p1 -b .coverity @@ -689,6 +693,10 @@ test -f %{sysconfig_anaconda} && \ %attr(0755,root,root) %{_libdir}/sshtest/sk-dummy.so %changelog +* Mon Oct 27 2025 Zoltan Fridrich - 9.9p1-15 +- Fix implicit destination path selection when source path ends with ".." + Resolves: RHEL-118406 + * Mon Sep 15 2025 Dmitry Belyavskiy - 9.9p1-14 - Relax GSS Kex restriction in FIPS mode Resolves: RHEL-91181 From bf1cef9a738592efe878c1d297b7c3a1a907b858 Mon Sep 17 00:00:00 2001 From: Zoltan Fridrich Date: Mon, 27 Oct 2025 15:59:58 +0100 Subject: [PATCH 2/2] Canonicalize username when matching a user Resolves: RHEL-101440 Signed-off-by: Zoltan Fridrich --- openssh-9.9p1-canonical-match-user.patch | 120 +++++++++++++++++++++++ openssh.spec | 4 + 2 files changed, 124 insertions(+) create mode 100644 openssh-9.9p1-canonical-match-user.patch diff --git a/openssh-9.9p1-canonical-match-user.patch b/openssh-9.9p1-canonical-match-user.patch new file mode 100644 index 0000000..1adf688 --- /dev/null +++ b/openssh-9.9p1-canonical-match-user.patch @@ -0,0 +1,120 @@ +diff --color -ruNp a/auth.c b/auth.c +--- a/auth.c 2025-10-29 09:57:20.440640281 +0100 ++++ b/auth.c 2025-10-29 09:57:54.747956792 +0100 +@@ -476,8 +476,12 @@ getpwnamallow(struct ssh *ssh, const cha + u_int i; + + ci = server_get_connection_info(ssh, 1, options.use_dns); +- ci->user = user; +- ci->user_invalid = getpwnam(user) == NULL; ++ pw = getpwnam(user); ++ if (pw != NULL && options.canonical_match_user) ++ ci->user = pw->pw_name; ++ else ++ ci->user = user; ++ ci->user_invalid = pw == NULL; + parse_server_match_config(&options, &includes, ci); + log_change_level(options.log_level); + log_verbose_reset(); +diff --color -ruNp a/servconf.c b/servconf.c +--- a/servconf.c 2025-10-29 09:57:20.502465586 +0100 ++++ b/servconf.c 2025-10-29 09:57:54.748205464 +0100 +@@ -225,6 +225,7 @@ initialize_server_options(ServerOptions + options->unused_connection_timeout = -1; + options->sshd_session_path = NULL; + options->refuse_connection = -1; ++ options->canonical_match_user = -1; + } + + /* Returns 1 if a string option is unset or set to "none" or 0 otherwise. */ +@@ -526,6 +527,8 @@ fill_default_server_options(ServerOption + options->sshd_session_path = xstrdup(_PATH_SSHD_SESSION); + if (options->refuse_connection == -1) + options->refuse_connection = 0; ++ if (options->canonical_match_user == -1) ++ options->canonical_match_user = 0; + + assemble_algorithms(options); + +@@ -609,7 +612,7 @@ typedef enum { + sAllowStreamLocalForwarding, sFingerprintHash, sDisableForwarding, + sExposeAuthInfo, sRDomain, sPubkeyAuthOptions, sSecurityKeyProvider, + sRequiredRSASize, sChannelTimeout, sUnusedConnectionTimeout, +- sSshdSessionPath, sRefuseConnection, ++ sSshdSessionPath, sRefuseConnection, sCanonicalMatchUser, + sDeprecated, sIgnore, sUnsupported + } ServerOpCodes; + +@@ -798,6 +801,7 @@ static struct { + { "unusedconnectiontimeout", sUnusedConnectionTimeout, SSHCFG_ALL }, + { "sshdsessionpath", sSshdSessionPath, SSHCFG_GLOBAL }, + { "refuseconnection", sRefuseConnection, SSHCFG_ALL }, ++ { "canonicalmatchuser", sCanonicalMatchUser, SSHCFG_GLOBAL }, + { NULL, sBadOption, 0 } + }; + +@@ -2807,6 +2811,11 @@ process_server_config_line_depth(ServerO + multistate_ptr = multistate_flag; + goto parse_multistate; + ++ case sCanonicalMatchUser: ++ intptr = &options->canonical_match_user; ++ multistate_ptr = multistate_flag; ++ goto parse_multistate; ++ + case sDeprecated: + case sIgnore: + case sUnsupported: +@@ -3028,6 +3037,7 @@ copy_set_server_options(ServerOptions *d + M_CP_INTOPT(required_rsa_size); + M_CP_INTOPT(unused_connection_timeout); + M_CP_INTOPT(refuse_connection); ++ M_CP_INTOPT(canonical_match_user); + + /* + * The bind_mask is a mode_t that may be unsigned, so we can't use +@@ -3368,6 +3378,7 @@ dump_config(ServerOptions *o) + dump_cfg_fmtint(sFingerprintHash, o->fingerprint_hash); + dump_cfg_fmtint(sExposeAuthInfo, o->expose_userauth_info); + dump_cfg_fmtint(sRefuseConnection, o->refuse_connection); ++ dump_cfg_fmtint(sCanonicalMatchUser, o->canonical_match_user); + + /* string arguments */ + dump_cfg_string(sPidFile, o->pid_file); +diff --color -ruNp a/servconf.h b/servconf.h +--- a/servconf.h 2025-10-29 09:57:20.503199202 +0100 ++++ b/servconf.h 2025-10-29 09:57:54.749236422 +0100 +@@ -261,6 +261,8 @@ typedef struct { + char *sshd_session_path; + + int refuse_connection; ++ ++ int canonical_match_user; + } ServerOptions; + + /* Information about the incoming connection as used by Match */ +diff --color -ruNp a/sshd_config.5 b/sshd_config.5 +--- a/sshd_config.5 2025-10-29 09:57:20.503465608 +0100 ++++ b/sshd_config.5 2025-10-29 09:57:54.749544972 +0100 +@@ -1384,6 +1384,7 @@ Available keywords are + .Cm PubkeyAuthentication , + .Cm PubkeyAuthOptions , + .Cm RefuseConnection , ++.Cm CanonicalMatchUser , + .Cm RekeyLimit , + .Cm RevokedKeys , + .Cm RDomain , +@@ -1828,6 +1829,13 @@ are enabled. + This option is only really useful in a + .Cm Match + block. ++.It Cm CanonicalMatchUser ++Some password databases allow users to define aliases for their username. ++This directive indicates that ++.Xr sshd 8 ++should attempt to first obtain a canonical username from a password database before evaluating a ++.Cm Match User ++conditional block. + .It Cm RekeyLimit + Specifies the maximum amount of data that may be transmitted or received + before the session key is renegotiated, optionally followed by a maximum diff --git a/openssh.spec b/openssh.spec index 3bf6a6c..68a04f2 100644 --- a/openssh.spec +++ b/openssh.spec @@ -219,6 +219,7 @@ Patch1028: openssh-9.9p1-fips-gss.patch #upstream 6432b9f6a216d0f5fb43df500e9bc30bebb3f58b #upstream 4f14ca8633a2c8c0a1a19165663421f0ab32f6ab Patch1029: openssh-9.9p1-scp-traversing.patch +Patch1030: openssh-9.9p1-canonical-match-user.patch License: BSD-3-Clause AND BSD-2-Clause AND ISC AND SSH-OpenSSH AND ssh-keyscan AND sprintf AND LicenseRef-Fedora-Public-Domain AND X11-distribute-modifications-variant Requires: /sbin/nologin @@ -413,6 +414,7 @@ gpgv2 --quiet --keyring %{SOURCE3} %{SOURCE1} %{SOURCE0} %patch -P 1027 -p1 -b .gss-indicators %patch -P 1028 -p1 -b .gss-fips %patch -P 1029 -p1 -b .scp-traversing +%patch -P 1030 -p1 -b .canonical-match-user %patch -P 100 -p1 -b .coverity @@ -696,6 +698,8 @@ test -f %{sysconfig_anaconda} && \ * Mon Oct 27 2025 Zoltan Fridrich - 9.9p1-15 - Fix implicit destination path selection when source path ends with ".." Resolves: RHEL-118406 +- Canonicalize username when matching a user + Resolves: RHEL-101440 * Mon Sep 15 2025 Dmitry Belyavskiy - 9.9p1-14 - Relax GSS Kex restriction in FIPS mode