[comp.lang.c] vsprintf considered dangerous

bobf@lotus.UUCP (Bob Frankston) (04/03/88)

One area that doesn't seem to be addressed in the ANSI standard is
the fact that C functions generally do not take lengths of output
buffers.  The result is that using the standard zero-terminated
string library functions can result in programs failing unexpectedly
on unusual input.

While it is easy to replace most string functions to avoid this,
vsprintf is much more difficult to replace.  It is also hard to
predict the required length for the buffer.  I feel very strongly
that there should be a new function that is like vsprintf but which
takes an output buffer length.  The standard should also say that the
implementation must not have any internal buffers that might get
overrun.  Of course, there should also be safe versions of the other
string functions, but that less critical.

Of course, the zero-termination is sacred so that a result that fills
the output buffer will be terminated by a zero byte within the
buffer.  For those not using zero-terminated strings there should be
corresponding "mem" functions.  In fact, a "mem" version of vsprintf
could be used by the "str" version and satisfy both needs.

rk9005@cca.ucsf.edu (Roland McGrath) (04/04/88)

The GNU C library has such a function.  It's called "vsnprintf" and it
takes a maximum-length argument (there's also an "snprintf").
When I finish the GNU C library it will be freely distributed and you
can then use it.

In general, one should take care when using the sprintf functions.
Make sure you know the maximum length that the format specification
will produce and arrange to have at least that much space allocated.
-- 
	Roland McGrath
ARPA: roland@rtsg.lbl.gov roland@lbl-rtsg.arpa
UUCP: ...!ucbvax!lbl-rtsg.arpa!roland

bromley@think.COM (Mark Bromley) (04/04/88)

In article <1219@ucsfcca.ucsf.edu> roland@rtsg.lbl.gov (Roland McGrath) writes:
>The GNU C library has such a function.  It's called "vsnprintf" and it
>takes a maximum-length argument (there's also an "snprintf").
>When I finish the GNU C library it will be freely distributed and you
>can then use it.
>
>In general, one should take care when using the sprintf functions.
>Make sure you know the maximum length that the format specification
>will produce and arrange to have at least that much space allocated.
>-- 


I have always disliked the sprintf and sscanf interface.  A somewhat different
interface would allow the standard functions to be used with complete error
checking in the case of io to/from strings.  In the context of stdio, what would
be provided is an sopen function, which is given a buffer and its size and
returns a stream in which i/o is done directly to the buffer.  Since the size is
given at open time, attempts to write outside the buffer can be trapped.

Implementing this on top of stdio should be almost trivial.  In fact, every
implementation of sprintf/sscanf that I have seen uses a mechanism almost
identical to this internally.

This method for dealing with i/o to strings is of course not original.  Common
Lisp provides the functions make-string-input-stream and
make-string-output-stream.  C++ provides essentially this functionality with a
somewhat cleaner syntax.

Mark Bromley

chris@mimsy.UUCP (Chris Torek) (04/04/88)

In article <18746@think.UUCP> bromley@think.COM (Mark Bromley) writes:
>... A somewhat different interface would allow the standard functions
>to be used with complete error checking in the case of io to/from
>strings.  In the context of stdio, what would be provided is an sopen
>function, which is given a buffer and its size and returns a stream in
>which i/o is done directly to the buffer.

% grep open /usr/include/stdio.h
#define	_IORW	0400		/* open for reading & writing */
extern FILE *fdopen(int fd, const char *type);
extern FILE *fopen(const char *name, const char *type);
extern FILE *freopen(const char *name, const char *type, FILE *stream);
extern FILE *popen(const char *name, const char *type);
extern FILE *fmemopen(char *addr, unsigned int len, const char *mode);
extern FILE *funopen(const void *cookie,
#define	fropen(cookie, fn) funopen(cookie, fn, 0, 0, 0)
#define fwopen(cookie, fn) funopen(cookie, 0, fn, 0, 0)

[non-prototype version deleted.  funopen is:
	FILE *funopen(const void *cookie,
		int (*readfn)(void *cookie, char *buf, int n),
		int (*writefn)(void *cookie, const char *buf, int n),
		long (*seekfn)(void *cookie, long off, int whence),
		int (*closefn)(void *cookie));
]

This is not standard anything, but rather my own hacks to stdio.
fmemopen() opens a memory region.  funopen() is an even more general
version that opens `I/O functions'.  Given this and vfprintf as a base,
one can write a completely portable version of, e.g., curses' `wprintw'
routine, with no limit on the number of characters that can be written.
(Writing more than 1K with wprintw or syslog, or 2K with XText*, has
been known to crash programs.)
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris

karl@haddock.ISC.COM (Karl Heuer) (04/05/88)

In article <1219@ucsfcca.ucsf.edu> roland@rtsg.lbl.gov (Roland McGrath) writes:
>[The GNU C library has snprintf() and vsnprintf()]

This problem came up some time ago in this newsgroup, and I thought I heard it
said that X3J11 recognized the need for a "safe" sprintf() and would be adding
snprintf() to the ANSI library.  Whatever happened to that proposal?

Karl W. Z. Heuer (ima!haddock!karl or karl@haddock.isc.com), The Walking Lint
(Note: comp.std.c is no longer moderated)

ok@quintus.UUCP (Richard A. O'Keefe) (04/05/88)

In article <18746@think.UUCP>, bromley@think.COM (Mark Bromley) writes:
> I have always disliked the sprintf and sscanf interface.  A somewhat different
> interface would allow the standard functions to be used with complete error
> checking in the case of io to/from strings.  In the context of stdio, what would
> be provided is an sopen function, which is given a buffer and its size and
> returns a stream in which i/o is done directly to the buffer.  Since the size is
> given at open time, attempts to write outside the buffer can be trapped.
> 
> Implementing this on top of stdio should be almost trivial.  In fact, every
> implementation of sprintf/sscanf that I have seen uses a mechanism almost
> identical to this internally.
> 
In 1984 I posted source code for such a thing.  Here it is again.
It isn't derived from anyone's proprietary source code, which means that
(a) you can do what you like with it, but
(b) it probably won't work in 4.[23]BSD or V.[23].
Have fun, anyway.

cat >fsopen.c <<'End-Of-File'
#include <stdio.h>

extern FILE *_lastbuf;

FILE *fsopen(area, size, mode)
    char *area;		/* the memory area to be read/written */
    int   size;		/* the size of the area in bytes      */
    char *mode;		/* "r", "w", or "a"		      */
    {
	register FILE *iop = &_iob[3];

	while (iop->_flag&(_IOREAD|_IOWRT|_IORW)) 
	    if (iop++ >= _lastbuf) return NULL;

	if (size < 0) {
	    register char *t = area;
	    register int n = 0;
	    while (*t++) n++;
	    size = n;
	}

	switch (*mode) {
	    case 'a':
		while (*area && size > 0) area++, size--;
	    case 'w':
		iop->_flag = _IOWRT|_IOSTRG;
		break;
	    case 'r':
		iop->_flag = _IOREAD|_IOSTRG;
		break;
	    default:
		return NULL;
	}

	iop->_ptr = iop->_base = area,
	iop->_cnt = size;
	return iop;
    }

End-Of-File

meissner@xyzzy.UUCP (Michael Meissner) (04/05/88)

In article <3312@haddock.ISC.COM> karl@haddock.ima.isc.com (Karl Heuer) writes:
| In article <1219@ucsfcca.ucsf.edu> roland@rtsg.lbl.gov (Roland McGrath) writes:
| >[The GNU C library has snprintf() and vsnprintf()]
| 
| This problem came up some time ago in this newsgroup, and I thought I heard it
| said that X3J11 recognized the need for a "safe" sprintf() and would be adding
| snprintf() to the ANSI library.  Whatever happened to that proposal?

It did not get the necessary 2/3 vote to add it, so the proposal was rejected.
-- 
Michael Meissner, Data General.		Uucp: ...!mcnc!rti!xyzzy!meissner
					Arpa/Csnet:  meissner@dg-rtp.DG.COM

daveb@geac.UUCP (David Collier-Brown) (04/07/88)

In article <18746@think.UUCP> bromley@lysippe.think.com.UUCP (Mark Bromley) writes:
| I have always disliked the sprintf and sscanf interface.  A somewhat different
| interface would allow the standard functions to be used with complete error
| checking in the case of io to/from strings.  

If memory serves, one can do this on Honeywell GCOS machines,
although probably only in "B" and not "C".  And no, I have no idea
why...
--dave
-- 
 David Collier-Brown.                 {mnetor yunexus utgpu}!geac!daveb
 Geac Computers International Inc.,   |  Computer Science loses its
 350 Steelcase Road,Markham, Ontario, |  memory (if not its mind) 
 CANADA, L3R 1B3 (416) 475-0525 x3279 |  every 6 months.

mouse@mcgill-vision.UUCP (der Mouse) (04/12/88)

In article <18746@think.UUCP>, bromley@think.COM (Mark Bromley) writes:
> In article <1219@ucsfcca.ucsf.edu> roland@rtsg.lbl.gov (Roland McGrath) writes:
>> The GNU C library has such a function.  It's called "vsnprintf" and
>> it takes a maximum-length argument (there's also an "snprintf").
> [...would prefer something like] an sopen function, which is given a
> buffer and its size and returns a stream in which i/o is done
> directly to the buffer.

I did this, though I called it fopenstr.  Very nice when you have a
function that takes a FILE * argument and outputs something to it, or
reads something from it, and you want it to talk to a string instead.
I also added

unfdopen()
	Undoes the effects of fdopen, that is, shut down a stdio stream
	without close(2)ing the underlying file descriptor.  Can't
	understand why it wasn't there to begin with.

fopenfxn()
	Creates a stream which calls a function to perform I/O.  For a
	read stream, the function should return one byte at a time; for
	a write stream, it gets called with each byte in turn.
	Extremely useful in some circumstances.  For example,  you have
	error messages generated in pieces all over the place and you
	want to prefix each line of error output with the program name
	and process ID (say).  Rather than try to figure out which of
	the error fprintfs are at the beginnings of lines, you just
	create a function stream whose function keeps a state variable
	and prints out the extra info when stuff is printed after a
	newline.

I also tossed vprintf, vfprintf, and vsprintf into a Berkeley stdio
(they were just popcorn routines though, they don't really count).

					der Mouse

			uucp: mouse@mcgill-vision.uucp
			arpa: mouse@larry.mcrcim.mcgill.edu

rbutterworth@watmath.waterloo.edu (Ray Butterworth) (04/25/88)

In article <2543@geac.UUCP>, daveb@geac.UUCP (David Collier-Brown) writes:
> In article <18746@think.UUCP> bromley@lysippe.think.com.UUCP (Mark Bromley) writes:
> | I have always disliked the sprintf and sscanf interface.  A somewhat different
> | interface would allow the standard functions to be used with complete error
> | checking in the case of io to/from strings.  
> If memory serves, one can do this on Honeywell GCOS machines,
> although probably only in "B" and not "C".  And no, I have no idea
> why...

The GCOS B and C libraries both take the fopen parameters
"ws", "rs", and "as", to mean that the "file-name" parameter is
really a pointer to an actual string to be written, read, or
appended to.  Unfortunately there is no check for overflow.