Bug 1283329 - RFC: ksh problem when restoring realloc'ed pointer
RFC: ksh problem when restoring realloc'ed pointer
Status: CLOSED CANTFIX
Product: Red Hat Enterprise Linux 6
Classification: Red Hat
Component: ksh (Show other bugs)
6.8
All All
unspecified Severity medium
: rc
: ---
Assigned To: Siteshwar Vashisht
BaseOS QE - Apps
:
Depends On:
Blocks: 1269194
  Show dependency treegraph
 
Reported: 2015-11-18 12:45 EST by Paulo Andrade
Modified: 2017-06-13 04:03 EDT (History)
0 users

See Also:
Fixed In Version:
Doc Type: If docs needed, set a value
Doc Text:
Story Points: ---
Clone Of:
Environment:
Last Closed: 2017-06-13 04:03:58 EDT
Type: Bug
Regression: ---
Mount Type: ---
Documentation: ---
CRM:
Verified Versions:
Category: ---
oVirt Team: ---
RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: ---


Attachments (Terms of Use)

  None (edit)
Description Paulo Andrade 2015-11-18 12:45:23 EST
I have a coredump where the only explanation I can
infer is a crash caused by restoring a pointer, in the
middle of a buffer that had become dangling, due to a
realloc.

  The code is in:

src/cmd/ksh93/sh/xec.c:sh_exec()

---8<---
int sh_exec(register const Shnode_t *t, int flags)
{
	register Shell_t	*shp = sh_getinterp();
	Stk_t			*stkp = shp->stk;
	int			unpipe=0;
	sh_sigcheck(shp);
	if(t && !shp->st.execbrk && !sh_isoption(SH_NOEXEC))
	{
		register int 	type = flags;
		register char	*com0 = 0;
		int 		errorflg = (type&sh_state(SH_ERREXIT))|OPTIMIZE;
		int 		execflg = (type&sh_state(SH_NOFORK));
		int 		execflg2 = (type&sh_state(SH_FORKED));
		int 		mainloop = (type&sh_state(SH_INTERACTIVE));
#if SHOPT_AMP || SHOPT_SPAWN
		int		ntflag = (type&sh_state(SH_NTFORK));
#else
		int		ntflag = 0;
#endif
		int		topfd = shp->topfd;
		char 		*sav=stkptr(stkp,0);
---8<---

  As you can see, the "sav" variable is local, and keeps
the return value of the stkptr call.
For more details:
#define	stkptr(sp,n)	((char*)((sp)->_data)+(n))

  Later, that value is restored, if not in "optimized" mode:
---8<---
#if SHOPT_COSHELL
		if(!shp->inpool && !(OPTIMIZE))
#else
		if(!(OPTIMIZE))
#endif /* SHOPT_COSHELL */
		{
			if(sav != stkptr(stkp,0))
				stkset(stkp,sav,0);
			else if(stktell(stkp))
				stkseek(stkp,0);
		}
---8<---

  The problem is that, in several constructs there are calls
in the pattern:
					struct checkpt *buffp = (struct checkpt*)stkalloc(shp->stk,sizeof(struct checkpt));

and stkalloc may call stkgrow, that may call realloc, and that
causes the local variable "sav" to possibly point to dangling
memory, if the realloc causes the base pointer to move.

  A kind of "blind" fix would be to save an offset to the base
of the stack, and restore it, e.g.:

    int offset=stktell(stkp);
....
    stkseek(stkp,offset);

but that may have side effects I did not consider, e.g. due to
the possibly being cast to "struct frame" and/or other uses.
Also, it is interesting to tell that, stkset will "reset" the
stack if the restored pointer is not in any frame of the stack,
when it is cast to "struct frame".

  The cause of the crash is that it looks as if there was an
unbalanced sh_pushcontext() ... sh_popcontext() sequence,
causing the sh_popcontext to have a NULL sigjump_buf, as
effectively, one of the sh_popcontext calls did:

shp->jmplist = shp->jmplist.prev;

but the prev field was NULL.


  I could not so far create a reproducer, only have the coredump
that leads to the logic described above, and that following the
code, is very likely the reason, as it does not have any kind
of protection for updating the "sav" pointer if the shp->stk
pointer is changed due to a realloc.

  Looking at the backtrace, it also appears to have "sav"
pointers out of the stack. Only frame 0, where the crash
happens, has sav inside the stack. It crashes because
shp->jmplist is NULL.

(gdb) frame 0
#0  sh_exec (t=0x7f34e32327f0, flags=516) at /usr/src/debug/ksh-20120801/src/cmd/ksh93/sh/xec.c:2306
2306				int  jmpval = ((struct checkpt*)shp->jmplist)->mode;
(gdb) p (sav >= shp->stk._data) & (sav < shp->stk._endb)
$17 = 1
(gdb) frame 1
#1  0x000000000045952b in sh_exec (t=0x7f34e3232ce0, flags=516) at /usr/src/debug/ksh-20120801/src/cmd/ksh93/sh/xec.c:2256
2256					sh_exec(t->lst.lstlef,errorflg|OPTIMIZE);
(gdb) p (sav >= shp->stk._data) & (sav < shp->stk._endb)
$18 = 0
(gdb) frame 2
#2  0x000000000045baa0 in sh_exec (t=0x7f34e3231020, flags=516) at /usr/src/debug/ksh-20120801/src/cmd/ksh93/sh/xec.c:2573
2573					sh_exec(t->if_.thtre,flags);
(gdb) p (sav >= shp->stk._data) & (sav < shp->stk._endb)
$19 = 0
(gdb) frame 3
#3  0x000000000045baa0 in sh_exec (t=0x7f34e3230ea0, flags=516) at /usr/src/debug/ksh-20120801/src/cmd/ksh93/sh/xec.c:2573
2573					sh_exec(t->if_.thtre,flags);
(gdb) p (sav >= shp->stk._data) & (sav < shp->stk._endb)
$20 = 0
(gdb) frame 4
#4  0x000000000045952b in sh_exec (t=0x7f34e3233e60, flags=516) at /usr/src/debug/ksh-20120801/src/cmd/ksh93/sh/xec.c:2256
2256					sh_exec(t->lst.lstlef,errorflg|OPTIMIZE);
(gdb) p (sav >= shp->stk._data) & (sav < shp->stk._endb)
$21 = 0
(gdb) frame 5
#5  0x00000000004588f9 in sh_exec (t=0x7f34e3230280, flags=4) at /usr/src/debug/ksh-20120801/src/cmd/ksh93/sh/xec.c:2401
2401					sh_exec(t->for_.fortre,flag);
(gdb) p (sav >= shp->stk._data) & (sav < shp->stk._endb)
$22 = 0
(gdb) frame 6
#6  0x000000000045952b in sh_exec (t=0x7f34e3234050, flags=6) at /usr/src/debug/ksh-20120801/src/cmd/ksh93/sh/xec.c:2256
2256					sh_exec(t->lst.lstlef,errorflg|OPTIMIZE);
(gdb) p (sav >= shp->stk._data) & (sav < shp->stk._endb)
$23 = 0

(Used & instead of && due to apparent gdb issue printing the expression)
Comment 1 Paulo Andrade 2015-11-18 13:00:52 EST
  I believe this is related to some other reports, where ksh
had just siglongjmp'ed to an "impossible" address, what ends
up with registers clobbered, and no way to figure out from
where it did jump.
Comment 3 Paulo Andrade 2015-11-18 22:09:41 EST
  From my understanding, the OPTIMIZE_FLAG value is
actually a "hidden" way to prevent the execution of

		if(!(OPTIMIZE))
#endif /* SHOPT_COSHELL */
		{
			if(sav != stkptr(stkp,0))
				stkset(stkp,sav,0);
			else if(stktell(stkp))
				stkseek(stkp,0);
		}


where OPTIMIZE is defined as
#   define OPTIMIZE		(flags&OPTIMIZE_FLAG)
and "force" it to only call sktset on the bottom of
the stack of called shell builtins or functions,
having the effect of hiding the bad pointer passed
to sktset. And, if a bad pointer is passed, it just
resets the stack; and, if at the bottom, it has the
effect of not crashing and continuing to work "correctly",
besides having earlier a crash condition.
  So, a proper test may require to have "flags&OPTIMIZE_FLAG"
always unset, what may require a special ksh build.

Note You need to log in before you can comment on or make changes to this bug.