From c69b79809a28d42ca59400cb3d4fcb35f3b96f82 Mon Sep 17 00:00:00 2001 From: Vincent Mihalkovic Date: Mon, 30 Mar 2026 13:24:03 +0200 Subject: [PATCH] Fix command substitution losing stdout after nested function redirects Resolves: RHEL-162296 --- ksh-1.0.11-comsub-nested-func.patch | 131 ++++++++++++++++++++++++++++ ksh.spec | 9 +- 2 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 ksh-1.0.11-comsub-nested-func.patch diff --git a/ksh-1.0.11-comsub-nested-func.patch b/ksh-1.0.11-comsub-nested-func.patch new file mode 100644 index 0000000..bd84a1e --- /dev/null +++ b/ksh-1.0.11-comsub-nested-func.patch @@ -0,0 +1,131 @@ +From 99028e9f709ee44f7c730d2883e31e37adab4d57 Mon Sep 17 00:00:00 2001 +From: Martijn Dekker +Date: Mon, 23 Mar 2026 22:45:04 +0000 +Subject: [PATCH] Fix bug in comsubs involving nested functions with redirects + +Port of upstream commit 99028e9 for ksh 1.0.6: add sh_redirect() comsub fork +workaround using flag==1/2 or string-buffer stdout (sfset & SF_STRING; 1.0.6 +sfio uses SF_STRING where newer trees use SFIO_STRING). 1.0.6 lacked the io.c +block present in newer branches. + +Upstream: https://github.com/ksh93/ksh/commit/99028e9f709ee44f7c730d2883e31e37adab4d57 + +--- + src/cmd/ksh93/sh/io.c | 27 +++++++++++ + src/cmd/ksh93/tests/subshell.sh | 66 ++++++++++++++++++++++++++++++ + 2 files changed, 93 insertions(+) + +diff --git a/src/cmd/ksh93/sh/io.c b/src/cmd/ksh93/sh/io.c +index 130b0368..ccea238a 100644 +--- a/src/cmd/ksh93/sh/io.c ++++ b/src/cmd/ksh93/sh/io.c +@@ -1135,6 +1135,33 @@ int sh_redirect(struct ionod *iop, int flag) + clexec = 1; + if(iop) + traceon = sh_trace(NULL,0); ++ /* ++ * A command substitution will hang on exit, writing infinite '\0', if, ++ * within it, standard output (FD 1) is redirected for a built-in command ++ * that calls sh_subfork(), or redirected permanently using 'exec' or ++ * 'redirect'. This forking workaround is necessary to avoid that bug. ++ * For shared-state comsubs, forking is incorrect, so error out then. ++ * TODO: actually fix the bug and remove this workaround. ++ */ ++ if(sh.subshell && sh.comsub && (flag==1 || flag==2 || (sfset(sfstdout,0,0) & SF_STRING))) ++ { ++ struct ionod *i; ++ for(i = iop; i; i = i->ionxt) ++ { ++ if((i->iofile & IOUFD) != 1) ++ continue; ++ if(!sh.subshare) ++ { ++ sh_subfork(); ++ break; ++ } ++ if(flag==1 || flag==2) ++ { ++ errormsg(SH_DICT,ERROR_exit(1),"cannot redirect stdout inside shared-state comsub"); ++ UNREACHABLE(); ++ } ++ } ++ } + for(;iop;iop=iop->ionxt) + { + iof=iop->iofile; +diff --git a/src/cmd/ksh93/tests/subshell.sh b/src/cmd/ksh93/tests/subshell.sh +index 0708c2ba..77c423d8 100755 +--- a/src/cmd/ksh93/tests/subshell.sh ++++ b/src/cmd/ksh93/tests/subshell.sh +@@ -1183,5 +1183,71 @@ exp='some output' + [[ $got == "$exp" ]] || err_exit 'command substitution did not catch output' \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + ++# ====== ++# Command substitution loses stdout after nested function redirects stdout to /dev/null ++ ++got=$( ++ function echo_devnull { ++ echo "DEVNULL" >/dev/null ++ } ++ function func { ++ echo_devnull >&2 ++ echo "FUNC" ++ } ++ OUT=$(func) ++ echo "OUT: ${OUT}" ++) ++exp='OUT: FUNC' ++[[ $got == "$exp" ]] || err_exit "comsub nested redirect test 1a (expected $(printf %q "$exp"), got $(printf %q "$got"))" ++ ++got=$( ++ function echo_devnull { ++ echo "DEVNULL" >/dev/null ++ } ++ function func { ++ echo_devnull >&2 ++ echo "FUNC" ++ } ++ OUT=${ func; } ++ echo "OUT: ${OUT}" ++) ++exp='OUT: FUNC' ++[[ $got == "$exp" ]] || err_exit "comsub nested redirect test 1b (expected $(printf %q "$exp"), got $(printf %q "$got"))" ++ ++got=$( ++ function echo_devnull { ++ ulimit -c 0 ++ } ++ function func { ++ echo_devnull >&2 ++ echo "FUNC" ++ } ++ OUT=$(func) ++ echo "OUT: ${OUT}" ++) ++exp='OUT: FUNC' ++[[ $got == "$exp" ]] || err_exit "comsub nested redirect test 2a (expected $(printf %q "$exp"), got $(printf %q "$got"))" ++ ++got=$( ++ function echo_devnull { ++ ulimit -c 0 ++ } ++ function func { ++ echo_devnull >&2 ++ echo "FUNC" ++ } ++ OUT=${ func; } ++ echo "OUT: ${OUT}" ++) ++exp='OUT: FUNC' ++[[ $got == "$exp" ]] || err_exit "comsub nested redirect test 2b (expected $(printf %q "$exp"), got $(printf %q "$got"))" ++ ++got=$( ++ OUT=$(ulimit -c 0 >&2; echo OK) ++ echo "OUT: ${OUT}" ++) ++exp='OUT: OK' ++[[ $got == "$exp" ]] || err_exit "comsub nested redirect test 3 (expected $(printf %q "$exp"), got $(printf %q "$got"))" ++ + # ====== + exit $((Errors<125?Errors:125)) diff --git a/ksh.spec b/ksh.spec index 9206817..17cfbac 100644 --- a/ksh.spec +++ b/ksh.spec @@ -4,7 +4,7 @@ URL: http://www.kornshell.com/ License: EPL-1.0 Epoch: 3 Version: 1.0.6 -Release: 15%{?dist} +Release: 16%{?dist} Source0: https://github.com/ksh93/%{name}/archive/v%{version}/%{name}-%{version}.tar.gz Source1: kshcomp.conf Source2: kshrc.rhs @@ -63,6 +63,9 @@ Patch15: ksh-1.0.12-security.patch # upstream commit: https://github.com/ksh93/ksh/commit/c2cfcfc6f4b2d63472b2290debb9d0bb84c932df Patch16: ksh-1.0.11-devfd-memory-fault.patch +# upstream commit: https://github.com/ksh93/ksh/commit/99028e9f709ee44f7c730d2883e31e37adab4d57 +Patch17: ksh-1.0.11-comsub-nested-func.patch + Conflicts: pdksh Requires: coreutils, diffutils BuildRequires: gcc @@ -184,6 +187,10 @@ fi %config(noreplace) %{_sysconfdir}/binfmt.d/kshcomp.conf %changelog +* Mon Mar 30 2026 Vincent Mihalkovic - 3:1.0.6-16 +- Fix command substitution losing stdout after nested function redirects + Resolves: RHEL-162296 + * Wed Feb 04 2026 Vincent Mihalkovic - 3:1.0.6-15 - Fix /dev/fd/1 memory fault and $0 for /dev/fd scripts Resolves: RHEL-146564