Bug 206770

Summary: 11.0-CURRENT/clang380-import: libc/stdio uninitialized pointer use (exposed via powerpc 32-bit context)
Product: Base System Reporter: Mark Millard <marklmi26-fbsd>
Component: binAssignee: freebsd-bugs (Nobody) <bugs>
Status: Closed Not Accepted    
Severity: Affects Only Me    
Priority: ---    
Version: CURRENT   
Hardware: Any   
OS: Any   

Description Mark Millard 2016-01-31 01:50:55 UTC
Using projects/clang380-import (-r294962 currently) I've been experimenting with a clang based powerpc (32-bit) buildworld (mixed with a gcc421 based buildkernel). Things boot and much operates but I ran into signals getting segmentation violations during signal handlers.

It turn out the ones that I've looked at so far are using routines similar to snprinf in the signal handlers and I've found that the call chains involved have an uninitialized pointer in use.

The following uses snprintf as a starting context but the more common central point is __vfprintf and what it does/calls for such "string output" routines.

Unfortunately the reason is spread out in the code so it takes a bit to describe the context for the uninitialized pointer that I expect is involved.

To start the description I note the actual, low-level failure point:

#0  0x419a89c8 in memcpy (dst0=0xffffd734, src0=<optimized out>, length=<optimized out>) at /usr/src/lib/libc/string/bcopy.c:124
124				TLOOP1(*--dst = *--src);

In the assembler code for this is the the *--src access that gets the segmentation violation. I do not justify that claim here but use that fact later.

So what leads up to that? Going the other way, starting from the use of snprintf. . .

snprintf(char * __restrict str, size_t n, char const * __restrict fmt, ...) sets up its __vfprintf(FILE *fp, locale_t locale, const char *fmt0, va_list ap) use via:

       va_list ap;
       FILE f = FAKE_FILE;
. . .
       va_start(ap, fmt);
       f._flags = __SWR | __SSTR;
       f._bf._base = f._p = (unsigned char *)str;
       f._bf._size = f._w = n;
       ret = __vfprintf(&f, __get_locale(), fmt, ap);

so at the __vfprintf call f._p reference the buffer that __vfprintf's str references. __vfprintf in turn does (in part):

       struct io_state io;     /* I/O buffering state */
. . .
       io_init(&io, fp);

where io is on-stack (not implicitly initialized). The io_init does:

#define NIOV 8
struct io_state {
       FILE *fp;
       struct __suio uio;      /* output information: summary */
       struct __siov iov[NIOV];/* ... and individual io vectors */
};

static inline void
io_init(struct io_state *iop, FILE *fp)
{

       iop->uio.uio_iov = iop->iov;
       iop->uio.uio_resid = 0;
       iop->uio.uio_iovcnt = 0;
       iop->fp = fp;
}

where (on stack as part of __vfprintf's io):

struct __siov {
       void    *iov_base;
       size_t  iov_len;
};
struct __suio {
       struct  __siov *uio_iov;
       int     uio_iovcnt;
       int     uio_resid;
};

So via __vfprintf's io.fp->_p the str buffer is accessible for outputting to.

But in none of this or other code that I've looked at for this snprintf use case have I found code that initializes the involved io.uio.uio_iov->iov_base (i.e., io.iov[0].iov_base) to point to anything specific. (Nor is iov_base's matching iov_len initialized.)

Here is a stab at finding all the initializations of iov_base fields:

# grep "iov_base.*=" /usr/src/lib/libc/stdio/*
/usr/src/lib/libc/stdio/fputs.c:        iov.iov_base = (void *)s;
/usr/src/lib/libc/stdio/fputws.c:       iov.iov_base = buf;
/usr/src/lib/libc/stdio/fwrite.c:       iov.iov_base = (void *)buf;
/usr/src/lib/libc/stdio/perror.c:               v->iov_base = (char *)s;
/usr/src/lib/libc/stdio/perror.c:               v->iov_base = ": ";
/usr/src/lib/libc/stdio/perror.c:       v->iov_base = msgbuf;
/usr/src/lib/libc/stdio/perror.c:       v->iov_base = "\n";
/usr/src/lib/libc/stdio/printfcommon.h: iop->iov[iop->uio.uio_iovcnt].iov_base = (char *)ptr;
/usr/src/lib/libc/stdio/puts.c: iov[0].iov_base = (void *)s;
/usr/src/lib/libc/stdio/puts.c: iov[1].iov_base = "\n";
/usr/src/lib/libc/stdio/putw.c: iov.iov_base = &w;
/usr/src/lib/libc/stdio/vfwprintf.c:    iov.iov_base = buf;
/usr/src/lib/libc/stdio/xprintf.c:      io->iovp->iov_base = __DECONST(void *, ptr);

The only file above involved in common for this context turns out to be: /usr/src/lib/libc/stdio/printfcommon.h and the above assignment in that file is in io_print(struct io_state *iop, const CHAR * __restrict ptr, int len, locale_t locale), which is not in use for this context. Here is that assignment anyway (just for reference):

static inline int
io_print(struct io_state *iop, const CHAR * __restrict ptr, int len, locale_t locale)
{

       iop->iov[iop->uio.uio_iovcnt].iov_base = (char *)ptr;
       iop->iov[iop->uio.uio_iovcnt].iov_len = len;
       iop->uio.uio_resid += len;
. . .

In other words: The segmentation violation is for dereferencing of __vfprintf's uninitialized io.uio.uio_iov->iov_base .

Returning to tracing the actually used code for this context to support that claim some more. . .

The __vfprintf (FILE *fp, locale_t locale, const char *fmt0, va_list ap) eventually does:

       if (io_flush(&io, locale))

and io_flush(struct io_state *iop, locale_t locale) does:

       return (__sprint(iop->fp, &iop->uio, locale));

and _sprintf(FILE *fp, struct __suio *uio, locale_t locale) does:

       err = __sfvwrite(fp, uio);

and __sfvwrite(FILE *fp, struct __suio *uio) does:

       p = iov->iov_base;
       len = iov->iov_len;

where  iov->iov_base is another name for __vfprintf's io.uio.uio_iov->iov_base . __sfvwrite then uses:

#define COPY(n)   (void)memcpy((void *)fp->_p, (void *)p, (size_t)(n))

which fails dereferencing p (i.e., dereferencing __vfprintf's io.uio.uio_iov->iov_base ). 

In other words (again): The segmentation violation is for dereferencing of the uninitialized __vfprintf io.uio.uio_iov->iov_base unless I've missed some initialization some place in the executing code for these sorts of "string output" contexts.
Comment 1 Mark Millard 2016-01-31 03:19:14 UTC
Hmm. Too much time at this I guess. . .

Reviewing again I do not find any __vfprintf paths that are without PRINT use (i.e., io_print use). That should mean that io.uio.uio_iov->iov_base was initialized but somehow changed.

I still have not replicated the problem with smaller/simpler code, only with libc/stdio use.

I'll have to try some more after a break.