Main Content

CWE Rule 676

Use of Potentially Dangerous Function

Since R2023a

Description

Rule Description

The program invokes a potentially dangerous function that could introduce a vulnerability if it is used incorrectly, but the function can also be used safely.

Polyspace Implementation

The rule checker checks for these issues:

  • Buffer overflow from incorrect string format specifier

  • Data race through standard library function call

  • Destination buffer overflow in string manipulation

  • Unsafe call to a system function

  • Unsafe conversion from string to numerical value

  • Use of dangerous standard function

  • Use of rand() for generating pseudorandom number

Examples

expand all

Issue

This issue occurs when the format specifier argument for functions such as sscanf leads to an overflow or underflow in the memory buffer argument.

Risk

If the format specifier specifies a precision that is greater than the memory buffer size, an overflow occurs. Overflows can cause unexpected behavior such as memory corruption.

Fix

Use a format specifier that is compatible with the memory buffer size.

Example — Memory Buffer Overflow
#include <stdio.h>

void func (char *str[]) {
    char buf[32];
    sscanf(str[1], "%33c", buf);  //Noncompliant
}

In this example, buf can contain 32 char elements. Therefore, the format specifier %33c causes a buffer overflow.

Correction — Use Smaller Precision in Format Specifier

One possible correction is to read a smaller number of elements into the buffer.

#include <stdio.h>

void func (char *str[]) {
    char buf[32];
    sscanf(str[1], "%32c", buf);
}
Issue

This checker is deactivated in a default Polyspace® as You Code analysis. See Checkers Deactivated in Polyspace as You Code Analysis (Polyspace Access).

This issue occurs when:

  1. Multiple tasks call the same standard library function.

    For instance, multiple tasks call the strerror function.

  2. The calls are not protected using a common protection.

    For instance, the calls are not protected by the same critical section.

Functions flagged by this defect are not guaranteed to be reentrant. A function is reentrant if it can be interrupted and safely called again before its previous invocation completes execution. If a function is not reentrant, multiple tasks calling the function without protection can cause concurrency issues. For the list of functions that are flagged, see CON33-C: Avoid race conditions when using library functions.

To find this defect, you must specify the multitasking options before analysis. To specify these options, on the Configuration pane, select Multitasking. For more information, see Configuring Polyspace Multitasking Analysis Manually.

Risk

The functions flagged by this defect are nonreentrant because their implementations can use global or static variables. When multiple tasks call the function without protection, the function call from one task can interfere with the call from another task. The two invocations of the function can concurrently access the global or static variables and cause unpredictable results.

The calls can also cause more serious security vulnerabilities, such as abnormal termination, denial-of-service attack, and data integrity violations.

Fix

To fix this defect, do one of the following:

  • Use a reentrant version of the standard library function if it exists.

    For instance, instead of strerror(), use strerror_r() or strerror_s(). For alternatives to functions flagged by this defect, see the documentation for CON33-C.

  • Protect the function calls using common critical sections or temporal exclusion.

    See Critical section details (-critical-section-begin -critical-section-end) and Temporally exclusive tasks (-temporal-exclusions-file).

    To identify existing protections that you can reuse, see the table and graphs associated with the result. The table shows each pair of conflicting calls. The Access Protections column shows existing protections on the calls. To see the function call sequence leading to the conflicts, click the icon. For an example, see below.

Example — Unprotected Call to Standard Library Function from Multiple Tasks
#include <errno.h>
#include <stdio.h>
#include <string.h>

void begin_critical_section(void);
void end_critical_section(void);

FILE *getFilePointer(void);

void func(FILE *fp) {
  fpos_t pos;
  errno = 0;
  if (0 != fgetpos(fp, &pos)) {
    char *errmsg = strerror(errno);  //Noncompliant
    printf("Could not get the file position: %s\n", errmsg);
  }
}

void task1(void) {
    FILE* fptr1 = getFilePointer();
    func(fptr1);
}

void task2(void) {
     FILE* fptr2 = getFilePointer();
     func(fptr2);
}

void task3(void) {
     FILE* fptr3 = getFilePointer();
     begin_critical_section();
     func(fptr3);
     end_critical_section();
}

In this example, to emulate multitasking behavior, specify the following options:

OptionSpecification
Configure multitasking manually
Tasks (-entry-points)

task1

task2

task3

Critical section details (-critical-section-begin -critical-section-end)Starting routineEnding routine
begin_critical_sectionend_critical_section

On the command-line, you can use the following:

 polyspace-bug-finder
   -entry-points task1,task2,task3
   -critical-section-begin begin_critical_section:cs1
   -critical-section-end end_critical_section:cs1

In this example, the tasks, task1, task2 and task3, call the function func. func calls the nonreentrant standard library function, strerror.

Though task3 calls func inside a critical section, other tasks do not use the same critical section. Operations in the critical section of task3 are not mutually exclusive with operations in other tasks.

These three tasks are calling a nonreentrant standard library function without common protection. In your result details, you see each pair of conflicting function calls.

If you click the icon, you see the function call sequence starting from the entry point to the standard library function call. You also see that the call starting from task3 is in a critical section. The Access Protections entry shows the lock and unlock function that begin and end the critical section. In this example, you see the functions begin_critical_section and end_critical_section.

Correction — Use Reentrant Version of Standard Library Function

One possible correction is to use a reentrant version of the standard library function strerror. You can use the POSIX® version strerror_r which has the same functionality but also guarantees thread-safety.

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

void begin_critical_section(void);
void end_critical_section(void);

FILE *getFilePointer(void);
enum { BUFFERSIZE = 64 };

void func(FILE *fp) {
  fpos_t pos;
  errno = 0;
  if (0 != fgetpos(fp, &pos)) {
    char errmsg[BUFFERSIZE];
    if (strerror_r(errno, errmsg, BUFFERSIZE) != 0) {
      /* Handle error */
    }
    printf("Could not get the file position: %s\n", errmsg);
  }
}

void task1(void) {
    FILE* fptr1 = getFilePointer();
    func(fptr1);
}

void task2(void) {
     FILE* fptr2 = getFilePointer();
     func(fptr2);
}

void task3(void) {
     FILE* fptr3 = getFilePointer();
     begin_critical_section();
     func(fptr3);
     end_critical_section();
}
Correction — Place Function Call in Critical Section

One possible correction is to place the call to strerror in critical section. You can implement the critical section in multiple ways.

For instance, you can place the call to the intermediate function func in the same critical section in the three tasks. When task1 enters its critical section, the other tasks cannot enter their critical sections until task1 leaves its critical section. The calls to func and therefore the calls to strerror from the three tasks cannot interfere with each other.

To implement the critical section, in each of the three tasks, call func between calls to begin_critical_section and end_critical_section.

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

void begin_critical_section(void);
void end_critical_section(void);

FILE *getFilePointer(void);

void func(FILE *fp) {
  fpos_t pos;
  errno = 0;
  if (0 != fgetpos(fp, &pos)) {
    char *errmsg = strerror(errno);
    printf("Could not get the file position: %s\n", errmsg);
  }
}

void task1(void) {
    FILE* fptr1 = getFilePointer();
    begin_critical_section();
    func(fptr1);
    end_critical_section();
}

void task2(void) {
     FILE* fptr2 = getFilePointer();
     begin_critical_section();
     func(fptr2);
     end_critical_section();
}

void task3(void) {
     FILE* fptr3 = getFilePointer();
     begin_critical_section();
     func(fptr3);
     end_critical_section();
}

Correction — Make Tasks Temporally Exclusive

Another possible correction is to make the tasks, task1, task2 and task3, temporally exclusive. Temporally exclusive tasks cannot execute concurrently.

On the Configuration pane, specify the following additional options:

On the command-line, you can use the following:

 polyspace-bug-finder
     -temporal-exclusions-file "C:\exclusions_file.txt"
where the file C:\exclusions_file.txt has the following line:
task1 task2 task3

Issue

This issue occurs when certain string manipulation functions write to their destination buffer argument at an offset greater than the buffer size.

For instance, when calling the function sprintf(char* buffer, const char* format), you use a constant string format of greater size than buffer.

Risk

Buffer overflow can cause unexpected behavior such as memory corruption or stopping your system. Buffer overflow also introduces the risk of code injection.

Fix

One possible solution is to use alternative functions to constrain the number of characters written. For instance:

  • If you use sprintf to write formatted data to a string, use snprintf, _snprintf or sprintf_s instead to enforce length control. Alternatively, use asprintf to automatically allocate the memory required for the destination buffer.

  • If you use vsprintf to write formatted data from a variable argument list to a string, use vsnprintf or vsprintf_s instead to enforce length control.

  • If you use wcscpy to copy a wide string, use wcsncpy, wcslcpy, or wcscpy_s instead to enforce length control.

Another possible solution is to increase the buffer size.

Example — Buffer Overflow in sprintf Use
#include <stdio.h>

void func(void) {
    char buffer[20];
    char *fmt_string = "This is a very long string, it does not fit in the buffer";

    sprintf(buffer, fmt_string);  //Noncompliant
}

In this example, buffer can contain 20 char elements but fmt_string has a greater size.

Correction — Use snprintf Instead of sprintf

One possible correction is to use the snprintf function to enforce length control.

#include <stdio.h>

void func(void) {
    char buffer[20];
    char *fmt_string = "This is a very long string, it does not fit in the buffer";

    snprintf(buffer, 20, fmt_string);
}
Issue

This issue occurs when you use a function that invokes an implementation-defined command processor. These functions include:

  • The C standard system() function.

  • The POSIX popen() function.

  • The Windows® _popen() and _wpopen() functions.

Risk

If the argument of a function that invokes a command processor is not sanitized, it can cause exploitable vulnerabilities. An attacker can execute arbitrary commands or read and modify data anywhere on the system.

Fix

Do not use a system-family function to invoke a command processor. Instead, use safer functions such as POSIX execve() and WinAPI CreateProcess().

Example — system() Called
# include <string.h>
# include <stdlib.h>
# include <stdio.h>
# include <unistd.h>

enum { 
SIZE512=512,
SIZE3=3};

void func(char *arg)
{
	char buf[SIZE512];
	int retval=snprintf(buf, sizeof(buf), "/usr/bin/any_cmd %s", arg);
	
	if (retval<=0 || retval>SIZE512){
		/* Handle error */
		abort();
	}
	/* Use of system() to pass any_cmd with 
	unsanitized argument to command processor */
	
	if (system(buf) == -1) {  //Noncompliant
	/* Handle error */
  }
} 

In this example, system() passes its argument to the host environment for the command processor to execute. This code is vulnerable to an attack by command-injection.

Correction — Sanitize Argument and Use execve()

In the following code, the argument of any_cmd is sanitized, and then passed to execve() for execution. exec-family functions are not vulnerable to command-injection attacks.

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

enum { 
SIZE512=512,
SIZE3=3};


void func(char *arg)
{
  char *const args[SIZE3] = {"any_cmd", arg, NULL};
  char  *const env[] = {NULL}; 
  
  /* Sanitize argument */
  
  /* Use execve() to execute any_cmd. */

  if (execve("/usr/bin/time", args, env) == -1) { 
    /* Handle error */
  }
} 
Issue

This issue occurs when you perform conversions from strings to integer or floating-point values and your conversion method does not include robust error handling.

Risk

Converting a string to numerical value can cause data loss or misinterpretation. Without validation of the conversion or error handling, your program continues with invalid values.

Fix
  • Add additional checks to validate the numerical value.

  • Use a more robust string-to-numeric conversion function such as strtol, strtoll, strtoul, or strtoull.

Example — Conversion With atoi
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static int demo_check_string_not_empty(char *s)
{
    if (s != NULL)
        return strlen(s) > 0; /* check string null-terminated and not empty */
    else
        return 0;
}

int unsafestrtonumeric(char* argv1)
{
    int s = 0;
    if (demo_check_string_not_empty(argv1))
    {
        s = atoi(argv1);  //Noncompliant
    }
    return s;
}

In this example, argv1 is converted to an integer with atoi. atoi does not provide errors for an invalid integer string. The conversion can fail unexpectedly.

Correction — Use strtol instead

One possible correction is to use strtol to validate the input string and the converted integer.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <errno.h>

static int demo_check_string_not_empty(char *s)
{
    if (s != NULL)
        return strlen(s) > 0; /* check string null-terminated and not empty */
    else
        return 0;
}

int unsafestrtonumeric(char *argv1)
{
    char *c_str = argv1;
    char *end;
    long sl;

    if (demo_check_string_not_empty(c_str))
    {
        errno = 0; /* set errno for error check */
        sl = strtol(c_str, &end, 10);
        if (end == c_str)
        {
            (void)fprintf(stderr, "%s: not a decimal number\n", c_str);
        }
        else if ('\0' != *end)
        {
            (void)fprintf(stderr, "%s: extra characters: %s\n", c_str, end);
        }
        else if ((LONG_MIN == sl || LONG_MAX == sl) && ERANGE == errno)
        {
            (void)fprintf(stderr, "%s out of range of type long\n", c_str);
        }
        else if (sl > INT_MAX)
        {
            (void)fprintf(stderr, "%ld greater than INT_MAX\n", sl);
        }
        else if (sl < INT_MIN)
        {
            (void)fprintf(stderr, "%ld less than INT_MIN\n", sl);
        }
        else
        {
            return (int)sl;
        }
    }
    return 0;
}
Issue

This issue occurs when your code uses standard functions that write data to a buffer in a way that can result in buffer overflows.

The following table lists dangerous standard functions, the risks of using each function, and what function to use instead. The checker flags:

  • Any use of an inherently dangerous function.

  • An use of a possibly dangerous function only if the size of the buffer to which data is written can be determined at compile time. The checker does not flag an use of such a function with a dynamically allocated buffer.

Dangerous FunctionRisk LevelSafer Function
getsInherently dangerous — You cannot control the length of input from the console.fgets
std::cin::operator>> and std::wcin::operator>>Inherently dangerous — You cannot control the length of input from the console.

Preface calls to cin by cin.width to control the input length. This method can result in truncated input.

To avoid potential buffer overflow and truncated input, use std::string objects as destinations for >> operator.

strcpyPossibly dangerous — If the size of the destination buffer is too small to accommodate the source buffer and a null terminator, a buffer overflow might occur. Use the function strlen() to determine the size of the source buffer, and allocate sufficient memory so that the destination buffer can accommodate the source buffer and a null terminator. Instead of strcpy, use the function strncpy.
stpcpyPossibly dangerous — If the source length is greater than the destination, buffer overflow can occur.stpncpy
lstrcpy or StrCpyPossibly dangerous — If the source length is greater than the destination, buffer overflow can occur.StringCbCopy, StringCchCopy, strncpy, strcpy_s, or strlcpy
strcatPossibly dangerous — If the concatenated result is greater than the destination, buffer overflow can occur.strncat, strlcat, or strcat_s
lstrcat or StrCatPossibly dangerous — If the concatenated result is greater than the destination, buffer overflow can occur.StringCbCat, StringCchCat, strncay, strcat_s, or strlcat
wcpcpyPossibly dangerous — If the source length is greater than the destination, buffer overflow can occur.wcpncpy
wcscatPossibly dangerous — If the concatenated result is greater than the destination, buffer overflow can occur.wcsncat, wcslcat, or wcncat_s
wcscpyPossibly dangerous — If the source length is greater than the destination, buffer overflow can occur.wcsncpy
sprintfPossibly dangerous — If the output length depends on unknown lengths or values, buffer overflow can occur.snprintf
vsprintfPossibly dangerous — If the output length depends on unknown lengths or values, buffer overflow can occur.vsnprintf
Risk

These functions can cause buffer overflow, which attackers can use to infiltrate your program.

Fix

The fix depends on the root cause of the defect. See fixes in the table above and code examples with fixes below.

If you do not want to fix the issue, add comments to your result or code to avoid another review. See:

Example — Using sprintf
#include <stdio.h>
#include <string.h>
#include <iostream>

#define BUFF_SIZE 128


int dangerous_func(char *str)
{
    char dst[BUFF_SIZE];
    int r = 0;

    if (sprintf(dst, "%s", str) == 1)  //Noncompliant
    {
        r += 1;
        dst[BUFF_SIZE-1] = '\0';
    }
    
    return r;
}

This example function uses sprintf to copy the string str to dst. However, if str is larger than the buffer, sprintf can cause buffer overflow.

Correction — Use snprintf with Buffer Size

One possible correction is to use snprintf instead and specify a buffer size.

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

#define BUFF_SIZE 128


int dangerous_func(char *str)
{
    char dst[BUFF_SIZE];
    int r = 0;

    if (snprintf(dst, sizeof(dst), "%s", str) == 1)
    {
        r += 1;
        dst[BUFF_SIZE-1] = '\0';
    }
    
    return r;
}
Issue

This issue occurs when you use the function rand for generating pseudorandom numbers.

Risk

The function rand is cryptographically weak. That is, the numbers generated by rand can be predictable. Do not use pseudorandom numbers generated from rand for security purposes. When a predictable random value controls the execution flow, your program is vulnerable to attacks.

Fix

Use more cryptographically sound pseudorandom number generators (PRNG), such as CryptGenRandom (Windows), OpenSSL/RAND_bytes(Linux/UNIX), or random (POSIX).

Example — Random Loop Numbers
#include <stdio.h>
#include <stdlib.h>

volatile int rd = 1;
int main(int argc, char *argv[])
{   
	int j, r, nloops;
	struct random_data buf;
	int i = 0;
	
	nloops = rand(); //Noncompliant
	
	for (j = 0; j < nloops; j++) {
		i = rand(); //Noncompliant
		printf("random_r: %ld\n", (long)i);
	}
	return 0;
}

This example uses rand to generate random numbers nloops and i. The predictability of these variables makes these function vulnerable to attacks.

Correction — Use Stronger PRNG

One possible correction is to replace the vulnerable PRNG with a stronger random number generator. For instance, this code uses the PRNG random() from POSIX library. random is a much stronger PRNG because it can be seeded by a different number every time it is called.

  
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define TIME_UTC 1
volatile int rd = 1;
int randomWrapper(){
	struct timespec ts;
  if (timespec_get(&ts, TIME_UTC) == 0) {
    /* Handle error */
  }
  srandom(ts.tv_nsec ^ ts.tv_sec);  /* Seed the PRNG */
	return random();
}
int main(int argc, char *argv[])
{   
    int j, r, nloops;
    struct random_data buf;
    int i = 0;
    
    nloops = randomWrapper();
    
    for (j = 0; j < nloops; j++) {
		i = randomWrapper();
        printf("random_r: %ld\n", (long)i);
    }
    return 0;
}

Check Information

Category: API / Function Errors

Version History

Introduced in R2023a