Warning C6029

Possible buffer overrun in call to 'function'

Possible buffer overrun in called function due to an unchecked buffer length/size parameter.

Remarks

This warning indicates that code passes an unchecked size to a function that takes a buffer and a size. The code doesn't verify that the data read from some external source is smaller than the buffer size. An attacker might intentionally specify a larger than expected value for the size, which can lead to a buffer overrun. Generally, whenever you read data from an untrusted external source, make sure to verify it for validity. It's appropriate to verify the size to make sure it's in the expected range.

Code analysis name: USING_TAINTED_DATA

Example

The following code generates this warning when it calls the annotated function std::fread two times. The code uses the first call to determine the length of the data to read in later calls. After the first call, analysis marks dataSize as coming from an untrusted source. Therefore, when the code passes the untrusted value to the second std::fread call, the analyzer generates this warning. A malicious actor could modify the file and cause the call to std::fread to overflow the buffer array. In real world code, you should also handle error recovery based on the return value of std::fread. For simplicity, these examples intentionally leave out error recovery code.

void processData(FILE* file)
{
    const size_t MAX_BUFFER_SIZE = 100;
    uint32_t buffer[MAX_BUFFER_SIZE]{};
    uint8_t dataSize = 0;

    // Read length data from the beginning of the file
    fread(&dataSize, sizeof(uint8_t), 1, file);
    // Read the rest of the data based on the dataSize
    fread(buffer, sizeof(uint32_t), dataSize, file);
}

The fix for the issue depends on the nature of the data and the behavior of the annotated function that triggers the diagnostic. For more information, see the documentation for that function. A straightforward fix is to check the size before the second call to std:fread. In the next example, we throw an exception to terminate the function. Most real-world code would instead have an error recovery strategy that's specific to the scenario.

void processData(FILE* file)
{
    const size_t MAX_BUFFER_SIZE = 100;
    uint32_t buffer[MAX_BUFFER_SIZE]{};
    uint8_t dataSize = 0;

    fread(&dataSize, sizeof(uint32_t), 1, file);

    if (dataSize > MAX_BUFFER_SIZE)
    {
        throw std::runtime_error("file data unexpected size");
    }

    fread(buffer, sizeof(uint32_t), dataSize, file);
}

In std:fread and similar functions, the code may need to read large amounts of data. To handle large data, you can allocate the size of the buffer dynamically after the size becomes known. Or, you can call std:fread multiple times as needed to read in the rest of the data. If you allocate the buffer dynamically, we recommend you put a limit on the size to avoid introducing an out-of-memory exploit for large values. We don't use this approach in our example because it's already bounded by the size of uint8_t.

void processDataDynamic(FILE* file)
{
    uint8_t dataSize = 0;
    fread(&dataSize, sizeof(uint8_t), 1, file);
    
    // Vector with `dataSize` default initialized objects
    std::vector<uint32_t> vecBuffer(dataSize);

    fread(&vecBuffer[0], sizeof(uint32_t), dataSize, file);
}
void processDataMultiple(FILE* file)
{
    const size_t MAX_BUFFER_SIZE = 100;
    uint32_t buffer[MAX_BUFFER_SIZE]{};
    uint8_t dataSize = 0;

    fread(&dataSize, sizeof(uint32_t), 1, file);

    while( dataSize > 0 )
    {
        size_t readSize = dataSize > MAX_BUFFER_SIZE ? MAX_BUFFER_SIZE : dataSize;
        readSize = fread(buffer, sizeof(uint32_t), readSize, file);
        dataSize = dataSize - readSize;
        // Process the data in `buffer`...
    }
}

See also

Rule sets for C++ code
Using SAL Annotations to reduce code defects