Bug 24988 - [RFE] snprintf must not fail with ENOMEM
Summary: [RFE] snprintf must not fail with ENOMEM
Status: NEW
Alias: None
Product: glibc
Classification: Unclassified
Component: stdio (show other bugs)
Version: 2.31
: P2 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2019-09-10 13:37 UTC by Carlos O'Donell
Modified: 2019-09-10 21:25 UTC (History)
1 user (show)

See Also:
Host:
Target:
Build:
Last reconfirmed:
fweimer: security-


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Carlos O'Donell 2019-09-10 13:37:16 UTC
Originally submitted to Fedora:
https://bugzilla.redhat.com/show_bug.cgi?id=441945

Description of problem: snprintf can fail with ENOMEM
Printing into a 200-byte buffer should never have to allocate an arbitrarily
large amount of memory.  In fact, snprintf should never allocate memory in a
manner that can fail, period.

Steps to Reproduce:
cat <<\EOF > k.c
/* snprintf should not allocate memory, *ever*.
   POSIX says that snprintf may fail with EOVERFLOW (n > INT_MAX or size of
   output would exceed INT_MAX).  It appears not to allow failure with ENOMEM,
   as happens here:

   $ zsh -c 'ulimit -v 5000; ./a.out %$[5*2**20]d'
   fmt: %5242880d  retval=-1  errno=Cannot allocate memory
   # Same with bash, but it requires more memory:
   $ bash -c 'ulimit -v 7000; ./a.out %$[12*2**20]d'
  */
#include <stdio.h>
#include <string.h>
#include <errno.h>
int
main(int argc, char **argv)
{
  char buf[200];
  char *fmt = argv[1];
  if (argc < 2)
    return 1;
  int n = snprintf (buf, sizeof buf, fmt, 1);
  int saved_errno = errno;
  printf ("fmt: %s  retval=%d  errno=%s\n", fmt, n,
	   n < 0 ? strerror(saved_errno) : "");
  return 0;
}
EOF
gcc k.c
zsh -c 'ulimit -v 5000; ./a.out %$[5*2**20]d'
  
Actual results:
fmt: %5242880d  retval=-1  errno=Cannot allocate memory

Expected results:
fmt: %5242880d  retval=5242880  errno=

Regarding POSIX conformance, consider this:
snprintf (NULL, 0, fmt, 1); currently, that fails just like the example above. 
However, POSIX requires something else:

   If the value of n is zero on a call to snprintf(),
   nothing shall be written, the number of bytes that
   would have been written had n been sufficiently large
   excluding the terminating null shall be returned, and
   s may be a null pointer.

That applies only upon successful completion.  Even for n 0 if there are errors
snprintf is supposed to return -1 and set errno.
For cases like:
#include <errno.h>
#include <locale.h>
#include <stdio.h>

int
main (void)
{
  setlocale (LC_ALL, "en_US.ISO-8859-1");
  int ret = snprintf (NULL, 0, "%lC\n", (wint_t) 0x10c);
  printf ("%d %m\n", ret);
  return 0;
}

where ret should be -1 and errno EILSEQ, but even cases where memory was needed
and -1/ENOMEM is returned.

That's a fine example, but it merely suggests that the crystal clear wording in
POSIX (quoted above) should be relaxed to allow snprintf to return -1 upon
EILSEQ.  There is already explicit language that implies snprintf w/n=0 will not
fail with ENOMEM, so at least in that latter case, glibc violates the intent of
the standard.

This is a RFE therefore to improve the QoI for the implementation to avoid allocations.
Comment 1 Joseph Myers 2019-09-10 21:25:21 UTC
Bugs 17829 and 21127 are other instances of this. (Indeed, the large-width case is mentioned in bug 17829 comment 7.)