Bug 1394329

Summary: The function sprintf in libc copies more characters than the format specification dictates
Product: Red Hat Enterprise Linux 5 Reporter: james.lemma
Component: glibcAssignee: Carlos O'Donell <codonell>
Status: CLOSED NOTABUG QA Contact: qe-baseos-tools-bugs
Severity: medium Docs Contact:
Priority: unspecified    
Version: 5.2CC: ashankar, fweimer, james.lemma, mnewsome, mpolacek, msebor, ohudlick, pfrankli
Target Milestone: rc   
Target Release: ---   
Hardware: i386   
OS: Linux   
Whiteboard:
Fixed In Version: Doc Type: If docs needed, set a value
Doc Text:
Story Points: ---
Clone Of: Environment:
Last Closed: 2016-11-22 16:28:34 UTC Type: Bug
Regression: --- Mount Type: ---
Documentation: --- CRM:
Verified Versions: Category: ---
oVirt Team: --- RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: --- Target Upstream Version:
Attachments:
Description Flags
C file that gives an example of the bug none

Description james.lemma 2016-11-11 18:00:23 UTC
Created attachment 1219832 [details]
C file that gives an example of the bug

Description of problem:
The libc function sprintf does not honor the length of format specification for long variables (writes additional characters if present despite length of format specification limiting them).

Version-Release number of selected component (if applicable):
RHEL 5.2 2.6.18-92.el5
gcc version 4.1.2 20071124 (Red Hat 4.1.2-42)
ldd (GNU libc) 2.5

How reproducible:
Compile and run attached sprintf_bug.c

Steps to Reproduce:
1. declare a long variable and set to a value that is longer than a character string variable (if converted using sprintf):

long longNum = 123456;
char longNumStr[4];

2. call memset to zero out character string variable:

memset(longNumStr, 0, sizeof(char) * 4);

3. call sprintf to convert long variable to character string variable, limiting number of characters to size of character string variable:

sprintf(longNumStr, "%3ld", longNum);

4. call printf to print out character string variable, noting that the value printed is longer than it should be based on format specification in sprintf call above:

printf("The contents of longNumStr is %s\n", longNumStr);

Actual results:
Prints string variable with additional characters beyond number specified in format specification in sprintf call (123456).

Expected results:
Prints string variable with number of characters as in format specification from sprintf call (123).

Additional info:
Output for longNumStr will be 123456, but it should be only 123. Side effect of this is that longNumStr is no longer null terminated and memory has been overwritten beyond the length of the string, this causes memory issues (segmentation faults in bigger, more complicated programs).

Comment 1 Florian Weimer 2016-11-22 16:28:34 UTC
The current version of the sprintf manual page is very explicit, explaining why this is expected behavior:

“
In no case does a nonexistent or small field width cause truncation of
a field; if the result of a conversion is wider than the field width,
the field is expanded to contain the conversion result.
”

If you want truncation and have only a single field, you can use snprintf instead.

Comment 2 Martin Sebor 2016-11-22 18:12:18 UTC
GCC 7.0 points out the problem in the test case the with a -Wformat-length warning:

$ cat rhbz-1394329.c && gcc -O2 -S -Wall rhbz-1394329.c 

#include <stdio.h>
#include <string.h>

int main(int argc, char **argv)
{
  long longNum = 123456;
  char longNumStr[4];

  memset(longNumStr, 0, sizeof(char) * 4);

  /* Should only copy over 3 digits from longNum, but will copy over 6 digits,
     ignoring length of format specification */
  sprintf(longNumStr, "%3ld", longNum);

  /* Output for longNumStr will be 123456, but it should be only 123.
     Side effect of this is that longNumStr is no longer null terminated
     and memory has been overwritten beyond the length of the string,
     this causes memory issues (segmentation faults in bigger, more
     complicated programs). */
  printf("The contents of longNumStr is %s\n", longNumStr);

  return 0;
}
rhbz-1394329.c: In function ‘main’:
rhbz-1394329.c:13:24: warning: ‘%3ld’ directive writing 6 bytes into a region of size 4 [-Wformat-length=]
   sprintf(longNumStr, "%3ld", longNum);
                        ^~~~
rhbz-1394329.c:13:3: note: format output 7 bytes into a destination of size 4
   sprintf(longNumStr, "%3ld", longNum);
   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~