CertC-POS30¶
Use the readlink() function properly
Required inputs: IR
eThe
readlink() function reads where a link points to. It makes
no effort to null-terminate its second argument,
buffer. Instead, it just returns the number of characters it has
written.
Noncompliant Code Example
If
len is equal to
sizeof(buf), the null terminator is written 1 byte past the
end of
buf:
char buf[1024];
ssize_t len = readlink("/usr/bin/perl", buf, sizeof(buf));
buf[len] = '\0';
An incorrect solution to this problem is to try to make
buf large enough that it can always hold the result:
long symlink_max;
size_t bufsize;
char *buf;
ssize_t len;
errno = 0;
symlink_max = pathconf("/usr/bin/", _PC_SYMLINK_MAX);
if (symlink_max == -1) {
if (errno != 0) {
/* handle error condition */
}
bufsize = 10000;
}
else {
bufsize = symlink_max+1;
}
buf = (char *)malloc(bufsize);
if (buf == NULL) {
/* handle error condition */
}
len = readlink("/usr/bin/perl", buf, bufsize);
buf[len] = '\0';
This modification incorrectly assumes that the symbolic link cannot be longer
than the value of
SYMLINK_MAX returned by
pathconf(). However, the value returned by
pathconf() is out of date by the time
readlink() is called, so the off-by-one buffer-overflow risk is
still present because, between the two calls, the location of
/usr/bin/perl can change to a file system with a larger
SYMLINK_MAX value. Also, if
SYMLINK_MAX is indeterminate (that is, if
pathconf() returned
-1 without setting
errno), the code uses an arbitrary large buffer size (10,000) that
it hopes will be sufficient, but there is a small chance that
readlink() can return exactly this size.
An additional issue is that
readlink() can return
-1 if it fails, causing an off-by-one underflow.
Compliant Solution
This compliant solution ensures there is no overflow by reading in only
sizeof(buf)-1 characters. It also properly checks to see if an
error has occurred:
enum { BUFFERSIZE = 1024 };
char buf[BUFFERSIZE];
ssize_t len = readlink("/usr/bin/perl", buf, sizeof(buf)-1);
if (len != -1) {
buf[len] = '\0';
}
else {
/* handle error condition */
}
Risk Assessment
Failing to properly null-terminate the result of
readlink() can result in abnormal program termination and
buffer-overflow vulnerabilities.
| Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
|---|---|---|---|---|---|
| POS30-C | high | probable | medium | P12 | L1 |
Related Guidelines
| Taxonomy | Taxonomy item | Relationship |
|---|---|---|
| CWE 2.11 | CWE-170, Improper null termination | 2017-06-13: CERT: Rule subset of CWE |
Bibliography
| [ Ilja 2006] |
| [ Open Group 1997a] |
| [ Open Group 2004] |
Possible Messages
Key |
Text |
Severity |
Disabled |
|---|---|---|---|
unhandled_return_value |
Missing check for the return value of readlink(). |
None |
False |
wrong_usage |
The buffer size argument in readlink() is expected to be in the format “sizeof(buffer) - 1”. |
None |
False |
Options¶
This rule shares the following common options: exclude_in_macros, exclude_messages_in_system_headers, excludes, extend_exclude_to_macro_invocations, includes, justification_checker, languages, post_processing, provider, report_at, severity
The following places define options that affect this rule: Stylechecks, Analysis-GlobalOptions
allow_assignment_to_globals¶
allow_assignment_to_globals : bool = False
allow_assignment_to_variables_with_pointers¶
allow_assignment_to_variables_with_pointers : bool = True
buffer_initialization_functions¶
buffer_initialization_functions : set[bauhaus.analysis.config.QualifiedName] = {'aligned_alloc', 'calloc', 'malloc', 'realloc'}
readlink for which applies
that the size of the buffer is the same as the used parameter.
functions¶
functions
Allows to declare function names for which a check must exist. The check is expressed as an IR pattern.Type: dict[bauhaus.analysis.config.QualifiedName, bauhaus.ir.common.algorithms.matchers.Matcher]
Default:
{ 'readlink': <bauhaus.rules.axivion.expressions.calls.unhandled_return_value.BinaryRelationAnyMatcher object at 0x7f6f177306a0> }
functions_under_test¶
functions_under_test : set[bauhaus.analysis.config.QualifiedName] = {'readlink'}
known_check_functions¶
known_check_functions : set[bauhaus.analysis.config.FunctionName] = set()