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]
Excerpt from SEI CERT C Coding Standard: Rules for Developing Safe, Reliable, and Secure Systems (2016 Edition) and SEI CERT C Coding Standard [https://cmu-sei.github.io/secure-coding-standards/sei-cert-c-coding-standard/rules/posix-pos/pos30-c], Copyright (C) 1995-2026 Carnegie Mellon University. See section 9.4. "3rd-Party Licenses" in the documentation for full details.

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

allow_assignment_to_globals

allow_assignment_to_globals : bool = False

Whether assignment to global / static variables should be allowed. If set to false, an error will be reported if the returned value is assigned to a global variable and any call is performed before checking the return (i.e., some other routine could access the return value before checking it).
 

allow_assignment_to_variables_with_pointers

allow_assignment_to_variables_with_pointers : bool = True

Whether assignment to variables of which the address has been taken somewhere should be allowed. If set to false, an error will be reported if the return value is assigned to such a variable, to ensure that the return value is checked locally, before any access from outside is possible.
 

buffer_initialization_functions

buffer_initialization_functions : set[bauhaus.analysis.config.QualifiedName] = {'aligned_alloc', 'calloc', 'malloc', 'realloc'}

Functions allowed to be used to initialize the buffer used for readlink for which applies that the size of the buffer is the same as the used parameter.
 

functions

functions

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>
}
Allows to declare function names for which a check must exist. The check is expressed as an IR pattern.
 

functions_under_test

functions_under_test : set[bauhaus.analysis.config.QualifiedName] = {'readlink'}

Readlink functions which should be used properly.
 

known_check_functions

known_check_functions : set[bauhaus.analysis.config.FunctionName] = set()

Collection of functions which are known to test return values of functions under test.