Never call a formatted I/O function with a format string
containing a tainted
value
. An attacker who can fully or partially control the contents of
a format string can crash a vulnerable process, view the contents of
the stack, view memory content, or write to an arbitrary memory
location. Consequently, the attacker can execute arbitrary
code with the permissions of the vulnerable process [Seacord
2013b]. Formatted output functions are particularly dangerous
because many programmers are unaware of their capabilities. For
example, formatted output functions can be used to write an integer
value to a specified address using the
%n
conversion specifier.
Noncompliant Code Example
The
incorrect_password()
function in this noncompliant code example is called during
identification and authentication to display an error message if
the specified user is not found or the password is incorrect. The
function accepts the name of the user as a string referenced by
user
. This is an exemplar of untrusted
data that originates from an unauthenticated user. The function
constructs an error message that is then output to
stderr
using the C Standard
fprintf()
function.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void
incorrect_password(
const
char
*user) {
int
ret;
/* User names are restricted
to 256 or fewer characters */
static
const
char
msg_format[] =
"%s cannot be authenticated.\n"
;
size_t
len =
strlen
(user) +
sizeof
(msg_format);
char
*msg = (
char
*)
malloc
(len);
if
(msg == NULL) {
/* Handle error */
}
ret = snprintf(msg, len,
msg_format, user);
if
(ret < 0) {
/* Handle error */
}
else
if
(ret >= len) {
/* Handle truncated output */
}
fprintf
(stderr, msg);
free
(msg);
}
|
The
incorrect_password()
function calculates the size of the message, allocates dynamic
storage, and then constructs the message in the allocated memory using
the
snprintf()
function. The addition operations are not checked for integer overflow
because the string referenced by
user
is known to have a length of 256 or less. Because the
%s
characters are replaced by the string referenced by
user
in the call to
snprintf()
, the resulting string needs 1 byte less than is allocated. The
snprintf()
function is commonly used for messages that are displayed in multiple
locations or messages that are difficult to build. However, the
resulting code contains a format-string vulnerability
because the
msg
includes untrusted user input and is passed as the format-string
argument in the call to
fprintf()
.
Compliant Solution (
fputs()
)
This compliant solution fixes the problem by replacing the
fprintf()
call with a call to
fputs()
, which outputs
msg
directly to
stderr
without evaluating its contents:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void
incorrect_password(
const
char
*user) {
int
ret;
/* User names are restricted
to 256 or fewer characters */
static
const
char
msg_format[] =
"%s cannot be authenticated.\n"
;
size_t
len =
strlen
(user) +
sizeof
(msg_format);
char
*msg = (
char
*)
malloc
(len);
if
(msg == NULL) {
/* Handle error */
}
ret = snprintf(msg, len,
msg_format, user);
if
(ret < 0) {
/* Handle error */
}
else
if
(ret >= len) {
/* Handle truncated output */
}
fputs
(msg, stderr);
free
(msg);
}
|
Compliant Solution (
fprintf()
)
This compliant solution passes the untrusted user input as one of the
variadic arguments to
fprintf()
and not as part of the format string, eliminating the possibility of a
format-string vulnerability:
#include <stdio.h>
void
incorrect_password(
const
char
*user) {
static
const
char
msg_format[] =
"%s cannot be authenticated.\n"
;
fprintf
(stderr, msg_format, user);
}
|
Noncompliant Code Example (POSIX)
This noncompliant code example is similar to the first noncompliant
code example but uses the POSIX function
syslog()
[IEEE
Std 1003.1:2013] instead of the
fprintf()
function. The
syslog()
function is also susceptible to format-string vulnerabilities.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
void
incorrect_password(
const
char
*user) {
int
ret;
/* User names are restricted
to 256 or fewer characters */
static
const
char
msg_format[] =
"%s cannot be authenticated.\n"
;
size_t
len =
strlen
(user) +
sizeof
(msg_format);
char
*msg = (
char
*)
malloc
(len);
if
(msg == NULL) {
/* Handle error */
}
ret = snprintf(msg, len,
msg_format, user);
if
(ret < 0) {
/* Handle error */
}
else
if
(ret >= len) {
/* Handle truncated output */
}
syslog(LOG_INFO, msg);
free
(msg);
}
|
The
syslog()
function first appeared in BSD 4.2 and is supported by Linux and other
modern UNIX implementations. It is not available on Windows systems.
Compliant Solution (POSIX)
This compliant solution passes the untrusted user input as one of the
variadic arguments to
syslog()
instead of including it in the format string:
#include <syslog.h>
void
incorrect_password(
const
char
*user) {
static
const
char
msg_format[] =
"%s cannot be authenticated.\n"
;
syslog(LOG_INFO, msg_format,
user);
}
|