From ripley at stats.ox.ac.uk  Sat Nov 15 05:54:20 2003
From: ripley at stats.ox.ac.uk (Prof Brian Ripley)
Date: Sat Nov 15 05:53:18 2003
Subject: [Rd] writeChar potential buffer overrun (PR#5090)
In-Reply-To: <20031114231706.GB20898@celos.net>
Message-ID: <Pine.LNX.4.44.0311150453430.27539-100000@gannet.stats>

Thanks, yes that all makes sense and checks out.
I decided to zero-pad the output, with a warning.

On Fri, 14 Nov 2003, Mark White wrote:

> Prof Brian Ripley writes:
> > Could you please give a reproducible example?
> 
> This breaks a new R session for me:
> 
>   zz <- file("/tmp/test.raw", "wb")
>   hdr <- ""
>   writeChar(hdr,zz,nchars=10000000)
> 
> Causes sig 11 on two machines, one with R 1.7.1 and the
> other with R 1.8.0 (both on NetBSD, so YMMV on other OSs, I
> suppose.)
> 
> > On Fri, 14 Nov 2003 mjw@celos.net wrote:
> > > Trying to copy the (binary) header of a input file directly
> > > to an output file, I've had repeatable seg faults.  The call:
> > > 
> > >   writeChar(hdr, outfh, nchars=6144)
> > > 
> > > when hdr just contains one empty string seems to be the
> > > culprit.  The stack traces weren't all that illuminating,
> > > with sig 11 in memory-related functions following this.  But
> > > in src/main/connections.c it looks like do_writechar doesn't
> > > check the length of strings when given an explicit nchars
> > > argument; so I think the strncpy() call will read too far.
> > 
> > All R strings should be null-terminated, so strncpy will only copy the
> > number of characters present (plus the null terminator) if less than n.
> 
> Quite true; I'd forgotten strncpy stopped at null.
> 
> > I can see that writeChars might write rubbish out, but not why it should 
> > segfault.  
> 
> Ok, I've just had a poke at it with ddd.  The above example
> faults in the memset() call (line 2772 connections.c) in both
> R versions.  I think the problem is underallocation of buf:
> 
>     len = 0;                            /* line 2757 */
>     for(i = 0; i < n; i++) {
> 	tlen = strlen(CHAR(STRING_ELT(object, i)));
> 	if (tlen > len) len = tlen;
>     }
>     buf = (char *) R_alloc(len + slen, sizeof(char));
> 
> which sets len to the longest string in object (in this
> case, 0 bytes), then allocates len+slen to buf.  gdb
> confirms len=0 and slen=1 at this point.  But a little later
> 
>     len = INTEGER(nchars)[i];           /* line 2770 */
>     s = CHAR(STRING_ELT(object, i));
>     memset(buf, '\0', len + slen);
>     strncpy(buf, s, len);
> 
> len is now set to [the first element of] nchars, which
> hasn't been checked, and is 10000000 (gdb confirms).  So the
> call to memset() copies way over the end of allocated buf.
> 
> Does that sound rational?  I'm not very familiar with R's
> internals.  I guess small overruns might not actually fault,
> because buf is within R's existing heap?
> 
> > It is also unclear to me what to do in this case: flag a user 
> > error?
> 
> I'm not sure; perhaps the most logical thing is indeed to
> pad with nulls (as I think it would do with this fault
> fixed), and not raise an error at all.
> 
> > > Using readBin and writeBin with integer() and size=1 seems
> > > to be the solution for header copying, but the faults still
> > > seemed worth reporting.
> > 
> > It's certainly the documented way.
> 
> Yup.  I tried readChar/writeChar thinking it might be
> quicker (no type conversion?) but it's obviously not
> feasible with null-terminated strings.
> 
> Mark <><
> 
> 

-- 
Brian D. Ripley,                  ripley@stats.ox.ac.uk
Professor of Applied Statistics,  http://www.stats.ox.ac.uk/~ripley/
University of Oxford,             Tel:  +44 1865 272861 (self)
1 South Parks Road,                     +44 1865 272866 (PA)
Oxford OX1 3TG, UK                Fax:  +44 1865 272595


[Rd] writeChar potential buffer overrun (PR#5090)
Prof Brian Ripley