Main Content

CWE Rule 686

Function Call With Incorrect Argument Type

Since R2023b

Description

Rule Description

The software calls a function, procedure, or routine, but the caller specifies an argument that is the wrong data type, which may lead to resultant weaknesses.

Polyspace Implementation

The rule checker checks for these issues:

  • Bad file access mode or status

  • Incorrect data type passed to va_arg

  • Standard function call with incorrect arguments

  • Use of automatic variable as putenv-family function argument

  • Writing to const qualified object

Examples

expand all

Issue

This issue occurs when you use functions in the fopen or open group with invalid or incompatible file access modes, file creation flags, or file status flags as arguments. For instance, for the open function, examples of valid:

  • Access modes include O_RDONLY, O_WRONLY, and O_RDWR

  • File creation flags include O_CREAT, O_EXCL, O_NOCTTY, and O_TRUNC.

  • File status flags include O_APPEND, O_ASYNC, O_CLOEXEC, O_DIRECT, O_DIRECTORY, O_LARGEFILE, O_NOATIME, O_NOFOLLOW, O_NONBLOCK, O_NDELAY, O_SHLOCK, O_EXLOCK, O_FSYNC, O_SYNC and so on.

The defect can occur in the following situations.

SituationRiskFix

You pass an empty or invalid access mode to the fopen function.

According to the ANSI® C standard, the valid access modes for fopen are:

  • r,r+

  • w,w+

  • a,a+

  • rb, wb, ab

  • r+b, w+b, a+b

  • rb+, wb+, ab+

fopen has undefined behavior for invalid access modes.

Some implementations allow extension of the access mode such as:

  • GNU®: rb+cmxe,ccs=utf

  • Visual C++®: a+t, where t specifies a text mode.

However, your access mode string must begin with one of the valid sequences.

Pass a valid access mode to fopen.
You pass the status flag O_APPEND to the open function without combining it with either O_WRONLY or O_RDWR.

O_APPEND indicates that you intend to add new content at the end of a file. However, without O_WRONLY or O_RDWR, you cannot write to the file.

The open function does not return -1 for this logical error.

Pass either O_APPEND|O_WRONLY or O_APPEND|O_RDWR as access mode.
You pass the status flags O_APPEND and O_TRUNC together to the open function.

O_APPEND indicates that you intend to add new content at the end of a file. However, O_TRUNC indicates that you intend to truncate the file to zero. Therefore, the two modes cannot operate together.

The open function does not return -1 for this logical error.

Depending on what you intend to do, pass one of the two modes.
You pass the status flag O_ASYNC to the open function. On certain implementations, the mode O_ASYNC does not enable signal-driven I/O operations.Use the fcntl(pathname, F_SETFL, O_ASYNC); instead.

Fix

The fix depends on the function and the flags used. 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 — Invalid Access Mode with fopen
#include <stdio.h>

void func(void) {
    FILE *file = fopen("data.txt", "rw"); //Noncompliant
    if(file!=NULL) {
        fputs("new data",file);
        fclose(file);
    }
}

In this example, the access mode rw is invalid. Because r indicates that you open the file for reading and w indicates that you create a new file for writing, the two access modes are incompatible.

Correction — Use Either r or w as Access Mode

One possible correction is to use the access mode corresponding to what you intend to do.

#include <stdio.h>

void func(void) {
    FILE *file = fopen("data.txt", "w");
    if(file!=NULL) {
        fputs("new data",file);
        fclose(file);
    }
}
Issue

This issue occurs when the data type in a va_arg call does not match the data type of the variadic function argument that va_arg reads.

For instance, you pass an unsigned char argument to a variadic function func. Because of default argument promotion, the argument is promoted to int. When you use a va_arg call that reads an unsigned char argument, a type mismatch occurs.

void func (int n, ...) {
   ...   
   va_list args;
   va_arg(args, unsigned char);
   ...   
}

void main(void) {
   unsigned char c;
   func(1,c);
}

Risk

In a variadic function (function with variable number of arguments), you use va_arg to read each argument from the variable argument list (va_list). The va_arg use does not guarantee that there actually exists an argument to read or that the argument data type matches the data type in the va_arg call. You have to make sure that both conditions are true.

Reading an incorrect type with a va_arg call can result in undefined behavior. Because function arguments reside on the stack, you might access an unwanted area of the stack.

Fix

Make sure that the data type of the argument passed to the variadic function matches the data type in the va_arg call.

Arguments of a variadic function undergo default argument promotions. The argument data types of a variadic function cannot be determined from a prototype. The arguments of such functions undergo default argument promotions (see Sec. 6.5.2.2 and 7.15.1.1 in the C99 Standard). Integer arguments undergo integer promotion and arguments of type float are promoted to double. For integer arguments, if a data type can be represented by an int, for instance, char or short, it is promoted to an int. Otherwise, it is promoted to an unsigned int. All other arguments do not undergo promotion.

To avoid undefined and implementation-defined behavior, minimize the use of variadic functions. Use the checkers for MISRA C:2012 Rule 17.1 or MISRA C++:2008 Rule 8-4-1 to detect use of variadic functions.

Example — char Used as Function Argument Type and va_arg argument
#include <stdarg.h>
#include <stdio.h>

unsigned char func(size_t count, ...) {
    va_list ap;
    unsigned char result = 0;
    va_start(ap, count);
    if (count > 0) {
        result = va_arg(ap, unsigned char); //Noncompliant
    }
    va_end(ap);
    return result;
}

void func_caller(void) {
    unsigned char c = 0x12;
    (void)func(1, c);
}

In this example, func takes an unsigned char argument, which undergoes default argument promotion to int. The data type in the va_arg call is still unsigned char, which does not match the int argument type.

Correction — Use int as va_arg Argument

One possible correction is to read an int argument with va_arg.

#include <stdarg.h>
#include <stdio.h>

unsigned char func(size_t count, ...) {
    va_list ap;
    unsigned char result = 0;
    va_start(ap, count);
    if (count > 0) {
        result = va_arg(ap, int); 
    }
    va_end(ap);
    return result;
}

void func_caller(void) {
    unsigned char c = 0x12;
    (void)func(1, c); 
}
Issue

This issue occurs when the arguments to certain standard functions do not meet the requirements for their use in the functions.

For instance, the arguments to these functions can be invalid in the following ways.

Function TypeSituationRiskFix
String manipulation functions such as strlen and strcpyThe pointer arguments do not point to a NULL-terminated string.The behavior of the function is undefined.Pass a NULL-terminated string to string manipulation functions.
File handling functions in stdio.h such as fputc and freadThe FILE* pointer argument can have the value NULL.The behavior of the function is undefined.Test the FILE* pointer for NULL before using it as function argument.
File handling functions in unistd.h such as lseek and read The file descriptor argument can be -1.

The behavior of the function is undefined.

Most implementations of the open function return a file descriptor value of -1. In addition, they set errno to indicate that an error has occurred when opening a file.

Test the return value of the open function for -1 before using it as argument for read or lseek.

If the return value is -1, check the value of errno to see which error has occurred.

The file descriptor argument represents a closed file descriptor.The behavior of the function is undefined.Close the file descriptor only after you have completely finished using it. Alternatively, reopen the file descriptor before using it as function argument.
Directory name generation functions such as mkdtemp and mkstempsThe last six characters of the string template are not XXXXXX.The function replaces the last six characters with a string that makes the file name unique. If the last six characters are not XXXXXX, the function cannot generate a unique enough directory name.Test if the last six characters of a string are XXXXXX before using the string as function argument.
Functions related to environment variables such as getenv and setenvThe string argument is "".The behavior is implementation-defined.Test the string argument for "" before using it as getenv or setenv argument.
The string argument terminates with an equal sign, =. For instance, "C=" instead of "C".The behavior is implementation-defined.Do not terminate the string argument with =.
String handling functions such as strtok and strstr

  • strtok: The delimiter argument is "".

  • strstr: The search string argument is "".

Some implementations do not handle these edge cases.Test the string for "" before using it as function argument.

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 — NULL Pointer Passed as strnlen Argument
#include <string.h>
#include <stdlib.h>

enum {
    SIZE10 = 10,
    SIZE20 = 20
};

int func() {
    char* s = NULL;
    return strnlen(s, SIZE20); //Noncompliant
}

In this example, a NULL pointer is passed as strnlen argument instead of a NULL-terminated string.

Before running analysis on the code, specify a GNU compiler. See Compiler (-compiler).

Correction — Pass NULL-terminated String

Pass a NULL-terminated string as the first argument of strnlen.

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

enum {
    SIZE10 = 10,
    SIZE20 = 20
};

int func() {
    char* s = "";
    return strnlen(s, SIZE20);
}
Issue

This issue occurs when the argument of a putenv-family function is a local variable with automatic duration.

Risk

The function putenv(char *string) inserts a pointer to its supplied argument into the environment array, instead of making a copy of the argument. If the argument is an automatic variable, its memory can be overwritten after the function containing the putenv() call returns. A subsequent call to getenv() from another function returns the address of an out-of-scope variable that cannot be dereferenced legally. This out-of-scope variable can cause environment variables to take on unexpected values, cause the program to stop responding, or allow arbitrary code execution vulnerabilities.

Fix

Use setenv()/unsetenv() to set and unset environment variables. Alternatively, use putenv-family function arguments with dynamically allocated memory, or, if your application has no reentrancy requirements, arguments with static duration. For example, a single thread execution with no recursion or interrupts does not require reentrancy. It cannot be called (reentered) during its execution.

Example — Automatic Variable as Argument of putenv()
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SIZE1024 1024

void func(int var)
{
    char env[SIZE1024];
    int retval = sprintf(env, "TEST=%s", var ? "1" : "0");
    if (retval <= 0) {
        /* Handle error */
    }
	/* Environment variable TEST is set using putenv().
	The argument passed to putenv is an automatic variable. */
    retval = putenv(env);    //Noncompliant 
    if (retval != 0) {
        /* Handle error */
    }
}
              

In this example, sprintf() stores the character string TEST=var in env. The value of the environment variable TEST is then set to var by using putenv(). Because env is an automatic variable, the value of TEST can change once func() returns.

Correction — Use static Variable for Argument of putenv()

Declare env as a static-duration variable. The memory location of env is not overwritten for the duration of the program, even after func() returns.

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

#define SIZE1024 1024 
void func(int var)
{
	/* static duration variable */
    static char env[SIZE1024]; 
    int retval = snprintf(env,"TEST=%s", var ? "1" : "0");
    if (retval <= 0) {
        /* Handle error */
    }
	
	/* Environment variable TEST is set using putenv() */
    retval=putenv(env);   
	if (retval != 0) {
        /* Handle error */
    }
}
Correction — Use setenv() to Set Environment Variable Value

To set the value of TEST to var, use setenv().

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

#define SIZE1024 1024 

void func(int var)
{
	/* Environment variable TEST is set using setenv() */
    int retval = setenv("TEST", var ? "1" : "0", 1); 
	
    if (retval != 0) {
        /* Handle error */
    }
}
Issue

This issue occurs when you do one of the following:

  • Use a const-qualified object as the destination of an assignment.

  • Pass a const-qualified object to a function that modifies the argument.

For instance, the defect can occur in the following situations:

  • You pass a const-qualified object as first argument of one of the following functions:

    • mkstemp

    • mkostemp

    • mkostemps

    • mkdtemp

  • You pass a const-qualified object as the destination argument of one of the following functions:

    • strcpy

    • strncpy

    • strcat

    • memset

  • You perform a write operation on a const-qualified object.

Risk

The risk depends upon the modifications made to the const-qualified object.

SituationRisk
Passing to mkstemp, mkostemp, mkostemps, mkdtemp, and so on.These functions replace the last six characters of their first argument with a string. Therefore, they expect a modifiable char array as their first argument.
Passing to strcpy, strncpy, strcat, memset and so on.These functions modify their destination argument. Therefore, they expect a modifiable char array as their destination argument.
Writing to the objectThe const qualifier implies an agreement that the value of the object will not be modified. By writing to a const-qualified object, you break the agreement. The result of the operation is undefined.
Fix

The fix depends on the modification made to the const-qualified object.

SituationFix
Passing to mkstemp, mkostemp, mkostemps, mkdtemp, and so on.Pass a non-const object as first argument of the function.
Passing to strcpy, strncpy, strcat, memset and so on.Pass a non-const object as destination argument of the function.
Writing to the objectPerform the write operation on a non-const object.

See examples of fixes below.

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

Example — Writing to const-Qualified Object
#include <string.h>

const char* buffer = "abcdeXXXXXXX";

void func(char* string) {
    char *ptr = (char*)strchr(buffer,'X');
    if(ptr)
        strcpy(ptr,string); //Noncompliant
}

In this example, because the pointer buffer is const-qualified, strchr(buffer,'X') returns a const-qualified char* pointer. When this char* pointer is used as the destination argument of strcpy, a Writing to const qualified object error appears.

Correction — Copy const-Qualified Object to Non-const Object

One possible correction is to assign the constant string to a non-const object and use the non-const object as destination argument of strchr.

#include <string.h>

char buffer[] = "abcdeXXXXXXX";

void func(char* string) { 
    char *ptr = (char*)strchr(buffer,'X');
    if(ptr)
        strcpy(ptr,string);
}

Check Information

Category: Others

Version History

Introduced in R2023b