ksh93 / ksh

ksh 93u+m: KornShell lives! | Latest release: https://github.com/ksh93/ksh/releases
Eclipse Public License 2.0
189 stars 31 forks source link

`typeset -p .sh.type` crashes after a defined type is instantiated #456

Closed JohnoKing closed 2 years ago

JohnoKing commented 2 years ago

This bug was originally reported at https://github.com/att/ast/issues/1297. Ksh will crash if typeset -p .sh.type is used after a type is instantiated:

$ cat /tmp/foo
typeset -p .sh.type
typeset -Ttyp1 typ1=(
    function get {
            .sh.value="'Sample'";
    }
)
typeset -p .sh.type
typ1
command typ1 var11
typ1
print $var11
print -v var11
typeset -p .sh.type

$ ksh /tmp/foo
namespace sh.type
{
    :
}
namespace sh.type
{
    typeset -r typ1='Sample'
}
/tmp/foo[9]: typ1: var11: only simple variables can be exported
typ1 var11='Sample'
'Sample'
'Sample'
Memory fault(coredump)

I ran the reproducer under ASan and initially got a buffer overflow crash caused by using memcmp instead of strncmp (a fix for that has been submitted in https://github.com/ksh93/ksh/pull/457). After that was dealt with, I got another buffer overflow crash in nv_getval:

$ arch/linux.i386-64/bin/ksh /tmp/foo
namespace sh.type
{
    :
}
namespace sh.type
{
    typeset -r typ1='Sample'
}
/tmp/foo[9]: typ1: var11: only simple variables can be exported
typ1 var11='Sample'
'Sample'
'Sample'
=================================================================
==43319==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000001230 at pc 0x5617d5cf8bb5 bp 0x7ffd4da1e070 sp 0x7ffd4da1e060
READ of size 4 at 0x602000001230 thread T0
    #0 0x5617d5cf8bb4 in nv_getval /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/name.c:2845
    #1 0x5617d5d553a0 in local_exports /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/xec.c:3058
    #2 0x5617d5cf53aa in scanfilter /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/name.c:2301
    #3 0x5617d5cf573a in nv_scan /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/name.c:2343
    #4 0x5617d5d56867 in sh_funscope /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/xec.c:3150
    #5 0x5617d5d59027 in sh_funct /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/xec.c:3320
    #6 0x5617d5d5a43f in sh_fun /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/xec.c:3401
    #7 0x5617d5c4a205 in lookup /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/nvdisc.c:416
    #8 0x5617d5c4ac63 in lookups /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/nvdisc.c:461
    #9 0x5617d5c466ba in nv_getv /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/nvdisc.c:60
    #10 0x5617d5cf824a in nv_getval /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/name.c:2782
    #11 0x5617d5d553a0 in local_exports /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/xec.c:3058
    #12 0x5617d5cf53aa in scanfilter /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/name.c:2301
    #13 0x5617d5cf573a in nv_scan /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/name.c:2343
    #14 0x5617d5d56867 in sh_funscope /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/xec.c:3150
    #15 0x5617d5d59027 in sh_funct /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/xec.c:3320
    #16 0x5617d5d5a43f in sh_fun /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/xec.c:3401
    #17 0x5617d5c4a205 in lookup /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/nvdisc.c:416
    #18 0x5617d5c4ac63 in lookups /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/nvdisc.c:461
    #19 0x5617d5c466ba in nv_getv /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/nvdisc.c:60
    #20 0x5617d5cf824a in nv_getval /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/name.c:2782
    #21 0x5617d5db2668 in print_namval /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/bltins/typeset.c:1519
    #22 0x5617d5db3244 in print_scan /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/bltins/typeset.c:1627
    #23 0x5617d5dab748 in print_value /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/bltins/typeset.c:597
    #24 0x5617d5dad2aa in setall /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/bltins/typeset.c:793
    #25 0x5617d5dab1ef in b_typeset /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/bltins/typeset.c:558
    #26 0x5617d5d462ff in sh_exec /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/xec.c:1384
    #27 0x5617d5c45120 in exfile /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/main.c:607
    #28 0x5617d5c42d14 in sh_main /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/main.c:368
    #29 0x5617d5c4104d in main /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/pmain.c:45
    #30 0x7f0c1bdb5b24 in __libc_start_main (/usr/lib/libc.so.6+0x27b24)
    #31 0x5617d5c40f5d in _start (/home/johno/GitRepos/KornShell/ksh/arch/linux.i386-64/bin/ksh+0x5cf5d)

0x602000001232 is located 0 bytes to the right of 2-byte region [0x602000001230,0x602000001232)
allocated by thread T0 here:
    #0 0x7f0c1c16e279 in __interceptor_malloc /build/gcc/src/gcc/libsanitizer/asan/asan_malloc_linux.cpp:145
    #1 0x5617d5c8b1ab in sh_malloc /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/init.c:251
    #2 0x5617d5cf312b in nv_putval /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/name.c:2016
    #3 0x5617d5d5549b in local_exports /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/xec.c:3060
    #4 0x5617d5cf53aa in scanfilter /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/name.c:2301
    #5 0x5617d5cf573a in nv_scan /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/name.c:2343
    #6 0x5617d5d56867 in sh_funscope /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/xec.c:3150
    #7 0x5617d5d59027 in sh_funct /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/xec.c:3320
    #8 0x5617d5d5a43f in sh_fun /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/xec.c:3401
    #9 0x5617d5c4a205 in lookup /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/nvdisc.c:416
    #10 0x5617d5c4ac63 in lookups /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/nvdisc.c:461
    #11 0x5617d5c466ba in nv_getv /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/nvdisc.c:60
    #12 0x5617d5cf824a in nv_getval /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/name.c:2782
    #13 0x5617d5db2668 in print_namval /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/bltins/typeset.c:1519
    #14 0x5617d5db3244 in print_scan /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/bltins/typeset.c:1627
    #15 0x5617d5dab748 in print_value /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/bltins/typeset.c:597
    #16 0x5617d5dad2aa in setall /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/bltins/typeset.c:793
    #17 0x5617d5dab1ef in b_typeset /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/bltins/typeset.c:558
    #18 0x5617d5d462ff in sh_exec /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/xec.c:1384
    #19 0x5617d5c45120 in exfile /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/main.c:607
    #20 0x5617d5c42d14 in sh_main /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/main.c:368
    #21 0x5617d5c4104d in main /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/pmain.c:45
    #22 0x7f0c1bdb5b24 in __libc_start_main (/usr/lib/libc.so.6+0x27b24)

SUMMARY: AddressSanitizer: heap-buffer-overflow /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/name.c:2845 in nv_getval
Shadow bytes around the buggy address:
  0x0c047fff81f0: fa fa fd fd fa fa fd fd fa fa fd fd fa fa 00 06
  0x0c047fff8200: fa fa 03 fa fa fa 02 fa fa fa 02 fa fa fa 00 04
  0x0c047fff8210: fa fa 02 fa fa fa 05 fa fa fa 05 fa fa fa 02 fa
  0x0c047fff8220: fa fa 00 04 fa fa 02 fa fa fa 06 fa fa fa 00 07
  0x0c047fff8230: fa fa 04 fa fa fa 02 fa fa fa 02 fa fa fa 03 fa
=>0x0c047fff8240: fa fa 00 05 fa fa[02]fa fa fa 04 fa fa fa 00 07
  0x0c047fff8250: fa fa 02 fa fa fa 00 04 fa fa 07 fa fa fa 06 fa
  0x0c047fff8260: fa fa 00 02 fa fa 00 01 fa fa 03 fa fa fa 00 07
  0x0c047fff8270: fa fa 07 fa fa fa 04 fa fa fa 00 07 fa fa 06 fa
  0x0c047fff8280: fa fa 05 fa fa fa 04 fa fa fa 02 fa fa fa 00 fa
  0x0c047fff8290: fa fa 02 fa fa fa 02 fa fa fa 06 fa fa fa 02 fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
  Shadow gap:              cc
==43319==ABORTING

The reproducer was also run under gdb without ASan enabled to produce this stacktrace:

(gdb) bt
#0  nv_getval (np=0x0) at /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/name.c:2758
#1  0x00005555555cc082 in sh_funscope (argn=1, argv=0x7fffffffcbe8, fun=0x0, arg=0x7fffffffca90, execflg=4) at /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/xec.c:3232
#2  0x00005555555cc4c6 in sh_funct (np=0x55555573d680, argn=1, argv=0x7fffffffcbe8, envlist=0x0, execflg=4) at /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/xec.c:3306
#3  0x00005555555ccac3 in sh_fun (np=0x55555573d680, nq=0x55555573f490, argv=0x7fffffffcbe8) at /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/xec.c:3387
#4  0x000055555556a312 in lookup (np=0x55555573f490, type=0, dp=0x0, handle=0x55555573dab0) at /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/nvdisc.c:416
#5  0x000055555556a6a0 in lookups (np=0x55555573f490, handle=0x55555573dab0) at /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/nvdisc.c:461
#6  0x0000555555568d3f in nv_getv (np=0x55555573f490, nfp=0x0) at /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/nvdisc.c:60
#7  0x00005555555a8944 in nv_getval (np=0x55555573f490) at /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/name.c:2780
#8  0x00005555555cb0db in local_exports (np=0x55555573f490, data=0x0) at /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/xec.c:3044
#9  0x00005555555a7853 in scanfilter (np=0x55555573f490, sp=0x7fffffffd020) at /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/name.c:2299
#10 0x00005555555a796d in nv_scan (root=0x5555557076a0, fn=0x5555555cb09b <local_exports>, data=0x0, mask=8192, flags=532480) at /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/name.c:2341
#11 0x00005555555cb7cf in sh_funscope (argn=1, argv=0x7fffffffd3f8, fun=0x0, arg=0x7fffffffd2a0, execflg=4) at /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/xec.c:3136
#12 0x00005555555cc4c6 in sh_funct (np=0x55555573d680, argn=1, argv=0x7fffffffd3f8, envlist=0x0, execflg=4) at /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/xec.c:3306
#13 0x00005555555ccac3 in sh_fun (np=0x55555573d680, nq=0x55555573d630, argv=0x7fffffffd3f8) at /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/xec.c:3387
#14 0x000055555556a312 in lookup (np=0x55555573d630, type=0, dp=0x0, handle=0x55555573d890) at /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/nvdisc.c:416
#15 0x000055555556a6a0 in lookups (np=0x55555573d630, handle=0x55555573d890) at /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/nvdisc.c:461
#16 0x0000555555568d3f in nv_getv (np=0x55555573d630, nfp=0x0) at /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/nvdisc.c:60
#17 0x00005555555a8944 in nv_getval (np=0x55555573d630) at /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/name.c:2780
#18 0x00005555555eacbb in print_namval (file=0x5555556e9b60 <_Sfstdout>, np=0x55555573d668, flag=0, tp=0x7fffffffd9d0) at /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/bltins/typeset.c:1519
#19 0x00005555555eb1f5 in print_scan (file=0x5555556e9b60 <_Sfstdout>, flag=524288, root=0x555555709280, option=0, tp=0x7fffffffd9d0) at /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/bltins/typeset.c:1627
#20 0x00005555555e7e9f in print_value (iop=0x5555556e9b60 <_Sfstdout>, np=0x555555709330, tp=0x7fffffffd9d0) at /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/bltins/typeset.c:597
#21 0x00005555555e8a29 in setall (argv=0x555555704330, flag=0, troot=0x5555557076a0, tp=0x7fffffffd9d0) at /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/bltins/typeset.c:793
#22 0x00005555555e7c87 in b_typeset (argc=3, argv=0x55555573d668, context=0x5555556ebb58 <sh+1464>) at /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/bltins/typeset.c:558
#23 0x00005555555c60dc in sh_exec (t=0x0, flags=5) at /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/xec.c:1370
#24 0x000055555556854f in exfile (iop=0x0, fno=1433654888) at /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/main.c:607
#25 0x0000555555567a83 in sh_main (ac=2, av=0x7fffffffe508, userinit=0x0) at /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/main.c:368
#26 0x0000555555566e0e in main (argc=2, argv=0x7fffffffe508) at /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/pmain.c:45
(gdb) frame 0
#0  nv_getval (np=0x0) at /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/name.c:2758
2758            if((!np->nvfun || !np->nvfun->disc) && !nv_isattr(np,NV_ARRAY|NV_INTEGER|NV_FUNCT|NV_REF))
(gdb) p np
$1 = (Namval_t *) 0x0
(gdb) frame 1
#1  0x00005555555cc082 in sh_funscope (argn=1, argv=0x7fffffffcbe8, fun=0x0, arg=0x7fffffffca90, execflg=4) at /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/xec.c:3232
3232            nv_getval(sh_scoped(IFSNOD));
(gdb) frame 2
#2  0x00005555555cc4c6 in sh_funct (np=0x55555573d680, argn=1, argv=0x7fffffffcbe8, envlist=0x0, execflg=4) at /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/xec.c:3306
3306                        sh_funscope(argn,argv,0,&fun,execflg);
(gdb) frame 3
#3  0x00005555555ccac3 in sh_fun (np=0x55555573d680, nq=0x55555573f490, argv=0x7fffffffcbe8) at /home/johno/GitRepos/KornShell/ksh/src/cmd/ksh93/sh/xec.c:3387
3387                        sh_funct(np,n,argv,(struct argnod*)0,sh_isstate(SH_ERREXIT));

For some reason sh_scoped() is returning a null pointer for IFS, which is likely related to the buffer overflow in the previous ASan crash. The patch below works around the null pointer, but isn't a correct fix:

diff --git a/src/cmd/ksh93/sh/name.c b/src/cmd/ksh93/sh/name.c
index c77eac35..df55b529 100644
--- a/src/cmd/ksh93/sh/name.c
+++ b/src/cmd/ksh93/sh/name.c
@@ -2621,9 +2621,10 @@ done:
  */
 Namval_t *sh_scoped(register Namval_t *np)
 {
-   if(!dtvnext(sh.var_tree))
+   Namval_t *mp;
+   if(!dtvnext(sh.var_tree) || !(mp = dtsearch(sh.var_tree,np)))
        return(np);
-   return(dtsearch(sh.var_tree,np));
+   return(mp);
 }

 #if SHOPT_OPTIMIZE

Results from the script with the workaround patch applied:

$ arch/*/bin/ksh /tmp/foo
namespace sh.type
{
    :
}
namespace sh.type
{
    typeset -r typ1='Sample'
}
/tmp/foo[9]: typ1: var11: only simple variables can be exported
typ1 var11='Sample'
'Sample'
'Sample'
namespace sh.type
{
    typeset -x -r typ1='Sample'
}
McDutchie commented 2 years ago

It looks like I just accidentally fixed this bug in 870ca92a0821775a400445126bc87ade6b203f94!

@JohnoKing, @hyenias, can you confirm?

McDutchie commented 2 years ago

It's particularly the change to local_exports() in xec.c that fixed it. Reverting that reintroduces the bug.

That change was:

    src/cmd/ksh93/sh/xec.c: local_exports():
    - Fix a separate bug exporting attributes to a new ksh function
      scope, which was previously masked by the other bug. The
      attributes (nvflag) were copied *after* nv_putval()ing the value,
      which is incorrect as the behaviour of nv_putval() is influenced
      by the attributes. But here, we're copying the value too, so we
      can simplify the whole function by using nv_clone() instead. This
      may also fix other corner cases. (re: c1994b87)

diff --git a/src/cmd/ksh93/sh/xec.c b/src/cmd/ksh93/sh/xec.c
index 0bfe05370..228a9a6fa 100644
--- a/src/cmd/ksh93/sh/xec.c
+++ b/src/cmd/ksh93/sh/xec.c
@@ -3047,15 +3047,9 @@ pid_t sh_fork(int flags, int *jobid)
 static void  local_exports(register Namval_t *np, void *data)
 {
    register Namval_t   *mp;
-   register char       *cp;
    NOT_USED(data);
-   if(nv_isarray(np))
-       nv_putsub(np,NIL(char*),0);
-   if((cp = nv_getval(np)) && (mp = nv_search(nv_name(np), sh.var_tree, NV_ADD|HASH_NOSCOPE)) && nv_isnull(mp))
-   {
-       nv_putval(mp, cp, 0);
-       mp->nvflag = np->nvflag;
-   }
+   if(!nv_isnull(np) && (mp = nv_search(nv_name(np), sh.var_tree, NV_ADD|HASH_NOSCOPE)) && nv_isnull(mp))
+       nv_clone(np,mp,0);
 }
McDutchie commented 2 years ago

So, since I found the exact cause of the fix, I'm closing this. If someone still finds a problem, please comment and I'll reopen.

McDutchie commented 2 years ago

Actually, I should commit a regression test before closing this.

McDutchie commented 2 years ago

Actually, this error message…

/tmp/foo[9]: typ1: var11: only simple variables can be exported

Isn't that a bug, too? There is no attempt to export anything in that reproducer script.

McDutchie commented 2 years ago

There is still something about typeset -p .sh.type that causes that bug – something is setting the NV_EXPORT attribute where it shouldn't.

typeset -Ttyp1 typ1=(
        function get {
                .sh.value="'Sample'";
        }
)
typeset -p .sh.type
typ1 var11

Output:

namespace sh.type
{
    typeset -r typ1='Sample'
}
foo[7]: typ1: var11: only simple variables can be exported

Commenting out the typeset -p .sh.type makes the spurious error go away.

McDutchie commented 2 years ago

The bug can be seen even more clearly this way:

typeset -Ttyp1 typ1=(
        function get {
                .sh.value="'Sample'";
        }
)
typ1 var11
typeset -p .sh.type
typeset -p .sh.type

Output:

namespace sh.type
{
    typeset -r typ1='Sample'
}
namespace sh.type
{
    typeset -x -r typ1='Sample'
}

The second typeset -p .sh.type makes an -x attribute appear out of nowhere.

McDutchie commented 2 years ago

I found the lines that cause the problem. Removing these two lines makes the spurious -x attribute go away:

--- a/src/cmd/ksh93/bltins/typeset.c
+++ b/src/cmd/ksh93/bltins/typeset.c
@@ -1523,8 +1523,6 @@ static int print_namval(Sfio_t *file,register Namval_t *np,register int flag, st
        print_value(file,np,tp);
        return(0);
    }
-   if(nv_isvtree(np))
-       nv_onattr(np,NV_EXPORT);
    if(cp=nv_getval(np))
    {
        if(indent)

But that does cause a couple of regressions:

test attributes begins at 2022-06-07+02:15:36
    attributes.sh[466]: FAIL: typeset -pC does not list only compound variables
test attributes failed at 2022-06-07+02:15:36 with exit code 1 [ 164 tests 1 error ]
test comvar begins at 2022-06-07+02:15:59
/usr/local/src/ksh93/ksh/src/cmd/ksh93/tests/comvar.sh[313]: eval: syntax error at line 1: `end of file' unexpected
/usr/local/src/ksh93/ksh/src/cmd/ksh93/tests/comvar.sh[314]: [[ (: not found
    comvar.sh[314]: FAIL: $x != $y with set | grep
test comvar failed at 2022-06-07+02:15:59 with exit code 1 [ 101 tests 1 error ]
McDutchie commented 2 years ago

The following seems to fix the spurious -x attribute bug, as well as the spurious error message in the original reproducer, without causing regressions. For reasons I don't understand yet, the NV_EXPORT attribute needs to be on during the nv_getval(np) call, presumably so that a discipline function that it triggers does the right thing. But that doesn't mean it should linger around if it wasn't set to begin with, so turn it back off if it was off.

diff --git a/src/cmd/ksh93/bltins/typeset.c b/src/cmd/ksh93/bltins/typeset.c
index f85a72eb2..2516330b8 100644
--- a/src/cmd/ksh93/bltins/typeset.c
+++ b/src/cmd/ksh93/bltins/typeset.c
@@ -1432,6 +1432,7 @@ static int print_namval(Sfio_t *file,register Namval_t *np,register int flag, st
 {
    register char *cp;
    int indent=tp->indent, outname=0, isfun;
+   char    tempexport=0;
    sh_sigcheck();
    if(flag)
        flag = '\n';
@@ -1523,9 +1524,15 @@ static int print_namval(Sfio_t *file,register Namval_t *np,register int flag, st
        print_value(file,np,tp);
        return(0);
    }
-   if(nv_isvtree(np))
+   if(nv_isvtree(np) && !nv_isattr(np,NV_EXPORT))
+   {
        nv_onattr(np,NV_EXPORT);
-   if(cp=nv_getval(np))
+       tempexport++;
+   }
+   cp=nv_getval(np);
+   if(tempexport)
+       nv_offattr(np,NV_EXPORT);
+   if(cp)
    {
        if(indent)
            sfnputc(file,'\t',indent);
McDutchie commented 2 years ago

Wow. Apparently, the NV_EXPORT attribute turns off indentation when outputting compound variables.

Normally:

$ ksh -c "typeset -C z=(foo=bar baz=quux); typeset -pC"
typeset -C z=(baz=quux;foo=bar)

After removing the nv_onattr(np,NV_EXPORT) from print_namval:

$ arch/*/bin/ksh -c "typeset -C z=(foo=bar baz=quux); typeset -pC"
typeset -C z=(
    baz=quux
    foo=bar
)

And this is where it's checked for: https://github.com/ksh93/ksh/blob/a1af93f32382dec17c83b84f98a133b0d425f548/src/cmd/ksh93/sh/nvtree.c#L1043

Well, that confirms that turning off the export attribute again after printing is the correct thing to do. But what an ugly hack, though. Again. And not a trace of an informative comment. Again. headdesk