The 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.
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.
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 */
}
|
Failing to properly null-terminate the result of readlink() can result in abnormal program termination and buffer-overflow vulnerabilities.
Rule | Severity | Likelihood | Detectable | Repairable | Priority | Level |
|---|---|---|---|---|---|---|
POS30-C | High | Probable | Yes | Yes | P18 | L1 |
Tool | Version | Checker | Description |
|---|---|---|---|
| Astrée | Supported: Can be checked with appropriate analysis stubs. | ||
| Axivion Bauhaus Suite | CertC-POS30 | ||
| CodeSonar | LANG.MEM.BO | Buffer Overrun | |
| Compass/ROSE | |||
| Coverity | READLINK | Implemented | |
| Helix QAC | C5033 | ||
| Klocwork | ABV.GENERAL | ||
| Parasoft C/C++test | CERT_C-POS30-a | Avoid overflow due to reading a not zero terminated string | |
| CERT C: Rule POS30-C | Checks for misuse of readlink() (rule partially covered) |
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Key here (explains table format and definitions)
Taxonomy | Taxonomy item | Relationship |
|---|---|---|
| CWE 2.11 | CWE-170, Improper null termination | 2017-06-13: CERT: Rule subset of CWE |
Key here for mapping notes
CWE-170 = Union( POS30-C, list) where list =
| [Ilja 2006] |
| [Open Group 1997a] |
| [Open Group 2004] |