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 332afca..140a06c 100644 --- a/openssh.spec +++ b/openssh.spec @@ -47,7 +47,7 @@ # Do not forget to bump pam_ssh_agent_auth release if you rewind the main package release to 1 %global openssh_ver 9.9p1 -%global openssh_rel 1 +%global openssh_rel 2 %global pam_ssh_agent_ver 0.10.4 %global pam_ssh_agent_rel 6 @@ -236,6 +236,9 @@ Patch1032: openssh-9.9p1-bad-hostkey.patch Patch1033: openssh-9.9p1-support-authentication-indicators-in-GSSAPI.patch # Patch1034: openssh-9.9p1-fips-gss.patch +#upstream 6432b9f6a216d0f5fb43df500e9bc30bebb3f58b +#upstream 4f14ca8633a2c8c0a1a19165663421f0ab32f6ab +Patch1035: openssh-9.9p1-scp-traversing.patch License: BSD Requires: /sbin/nologin @@ -446,6 +449,7 @@ popd %patch1032 -p1 -b .bad-hostkey %patch1033 -p1 -b .gss-indicators %patch1034 -p1 -b .gss-fips +%patch1035 -p1 -b .scp-traversing autoreconf pushd pam_ssh_agent_auth-pam_ssh_agent_auth-%{pam_ssh_agent_ver} @@ -733,6 +737,10 @@ test -f %{sysconfig_anaconda} && \ %endif %changelog +* Mon Oct 27 2025 Zoltan Fridrich - 9.9p1-2 +- Fix implicit destination path selection when source path ends with ".." + Resolves: RHEL-119515 + * Wed Sep 10 2025 Pavol Žáčik - 9.9p1-1 - Rebase to version 9.9 Resolves: RHEL-108912