Main Content

CWE Rule 547

Use of Hard-coded, Security-relevant Constants

Since R2023a

Description

Rule Description

The program uses hard-coded constants instead of symbolic names for security-critical values, which increases the likelihood of mistakes during code maintenance or security policy change.

Polyspace Implementation

The rule checker checks for these issues:

  • Hard-coded buffer size

  • Hard-coded loop boundary

  • Hard-coded object size used to manipulate memory

  • Hard-coded sensitive data

Examples

expand all

Issue

This issue occurs when you use a numerical value instead of a symbolic constant when declaring a memory buffer such as an array.

Risk

Hard-coded buffer size causes the following issues:

  • Hard-coded buffer size increases the likelihood of mistakes and therefore maintenance costs. If a policy change requires developers to change the buffer size, they must change every occurrence of the buffer size in the code.

  • Hard-constant constants can be exposed to attack if the code is disclosed.

Fix

Use a symbolic name instead of a hard-coded constant for buffer size. Symbolic names include const-qualified variables, enum constants, or macros.

enum constants are recommended.

  • Macros are replaced by their constant values after preprocessing. Therefore, they can expose the loop boundary.

  • enum constants are known at compilation time. Therefore, compilers can optimize the loops more efficiently.

    const-qualified variables are usually known at run time.

Example — Hard-Coded Buffer Size
int table[100];  //Noncompliant

void read(int);

void func(void) {
    for (int i=0; i<100; i++) //Noncompliant
        read(table[i]);
}

In this example, the size of the array table is hard-coded. There is a second violation of the rule in this example, because the loop boundary of the for loop is also hard-coded.

Correction — Use Symbolic Name

One possible correction is to replace the hard-coded size with a symbolic name.

const int MAX_1 = 100;        
#define MAX_2 100
enum { MAX_3 = 100 };

int table_1[MAX_1];
int table_2[MAX_2];
int table_3[MAX_3];

void read(int);

void func(void) {
    for (int i=0; i < MAX_1; i++)
        read(table_1[i]);
    for (int i=0; i < MAX_2; i++)
        read(table_2[i]);
    for (int i=0; i < MAX_3; i++)
        read(table_3[i]);
}
Issue

This issue occurs when you use a numerical value instead of symbolic constant for the boundary of a for, while or do-while loop.

Risk

Hard-coded loop boundary causes the following issues:

  • Hard-coded loop boundary makes the code vulnerable to denial of service attacks when the loop involves time-consuming computation or resource allocation.

  • Hard-coded loop boundary increases the likelihood of mistakes and maintenance costs. If a policy change requires developers to change the loop boundary, they must change every occurrence of the boundary in the code.

    For instance, the loop boundary is 10000 and represents the maximum number of client connections supported in a network server application. If the server supports more clients, you must change all instances of the loop boundary in your code. Even if the loop boundary occurs once, you have to search for a numerical value of 10000 in your code. The numerical value can occur in places other than the loop boundary. You must browse through those places before you find the loop boundary.

Fix

Use a symbolic name instead of a hard-coded constant for loop boundary. Symbolic names include const-qualified variables, enum constants or macros.enum constants are recommended because:

  • Macros are replaced by their constant values after preprocessing. Therefore, they can expose the buffer size.

  • enum constants are known at compilation time. Therefore, compilers can allocate storage for them more efficiently.

    const-qualified variables are usually known at run time.

Example — Hard-Coded Loop Boundary
void performOperation(int);

void func(void) {
    for (int i=0; i<100; i++)  //Noncompliant
        performOperation(i);
}

In this example, the boundary of the for loop is hard-coded.

Correction — Use Symbolic Name

One possible correction is to replace the hard-coded loop boundary with a symbolic name.

const int MAX_1 = 100;
#define MAX_2 100
enum { MAX_3 = 100 };

void performOperation_1(int);
void performOperation_2(int);
void performOperation_3(int);

void func(void) {
    for (int i=0; i<MAX_1; i++)
        performOperation_1(i);
    for (int i=0; i<MAX_2; i++)
        performOperation_2(i);
    for (int i=0; i<MAX_3; i++)
        performOperation_3(i);
}
Issue

This issue occurs when you use hard-coded constants as memory size arguments for these memory functions:

  • Dynamic memory allocation function such as malloc or calloc.

  • Memory manipulation functions such as memcpy, memmove, memcmp, or memset.

When performing memory operations with a string literal, Polyspace® does not report a defect if you hard code the memory size.

Risk

If you hard code object size, your code is not portable to architectures with different type sizes. If the constant value is not the same as the object size, the buffer might or might not overflow.

Fix

For the size argument of memory functions, use sizeof(object).

Example — Assume 4-Byte Integer Pointers
#include <stddef.h>
#include <stdlib.h>
enum {
    SIZE3   = 3,
    SIZE20  = 20
};
extern void fill_ints(int **matrix, size_t nb, size_t s);

void bug_hardcodedmemsize()
{
    size_t i, s;

    s = 4;
    int **matrix = (int **)calloc(SIZE20, s);  //Noncompliant
    if (matrix == NULL) {
        return; /* Indicate calloc() failure */
    }
    fill_ints(matrix, SIZE20, s);
    free(matrix);
}

In this example, the memory allocation function calloc is called with a memory size of 4. The memory is allocated for an integer pointer, which can be a more or less than 4 bytes depending on your target. If the integer pointer is not 4 bytes, your program can fail.

Correction — Use sizeof(int *)

When calling calloc, replace the hard-coded size with a call to sizeof. This change makes your code more portable.

#include <stddef.h>
#include <stdlib.h>
enum {
    SIZE3   = 3,
    SIZE20  = 20
};
extern void fill_ints(int **matrix, size_t nb, size_t s);

void corrected_hardcodedmemsize()
{
    size_t i, s;

    s = sizeof(int *);
    int **matrix = (int **)calloc(SIZE20, s);
    if (matrix == NULL) {
        return; /* Indicate calloc() failure */
    }
    fill_ints(matrix, SIZE20, s);
    free(matrix);
}

Issue

This issue occurs when data that is potentially sensitive is directly exposed in the code, for instance, as string literals. The checker identifies certain data as sensitive from their use in certain functions such as password encryption functions.

Following data can be potentially sensitive.

Type of DataFunctions That Indicate Sensitive Nature of Information
Host name
  • sethostname, setdomainname, gethostbyname, gethostbyname2, getaddrinfo, gethostbyname_r, gethostbyname2_r (string argument)

  • inet_aton, inet_pton, inet_net_pton, inet_addr, inet_network (string argument)

  • mysql_real_connect, mysql_real_connect_nonblocking, mysql_connect (2nd argument)

Password
  • CreateProcessWithLogonW, LogonUser (1st argument)

  • mysql_real_connect, mysql_real_connect_nonblocking, mysql_connect (3rd argument)

Database
  • MySQL: mysql_real_connect, mysql_real_connect_nonblocking, mysql_connect (4th argument)

  • SQLite: sqlite3_open, sqlite3_open16, sqlite3_open_v2 (1st argument)

  • PostgreSQL: PQconnectdb

  • Microsoft SQL: SQLDriverConnect (3rd argument)

User name
  • getpw, getpwnam, getpwnam_r, getpwuid, getpwuid_r

Saltcrypt, crypt_r (2nd argument)
Cryptography keys and initialization vectors

OpenSSL:

  • EVP_CipherInit, EVP_EncryptInit, EVP_DecryptInit (3rd argument)

  • EVP_CipherInit_ex, EVP_EncryptInit_ex, EVP_DecryptInit_ex (4th argument)

Seed
  • srand, srandom, initstate (1st argument)

  • OpenSSL: RAND_seed, RAND_add

Risk

Information that is hardcoded can be queried from binaries generated from the code.

Fix

Avoid hard coding sensitive information.

Example — Sensitive Data Exposed Through String Literals
// Typically, you include the header "mysql.h" with function and type declarations.
// In this example, only the required lines from the header are quoted.

typedef struct _MYSQL MYSQL;

MYSQL *mysql_real_connect(MYSQL *mysql,
                          const char *host, const char *user, const char *passwd,
                          const char *db, unsigned int port, const char *unix_socket,
                          unsigned long client_flag);

typedef void * DbHandle;
extern MYSQL *sql;

// File that uses functions from "mysql.h" 
const char *host = "localhost";
char *user = "guest";
char *passwd;

DbHandle connect_to_database_server(const char *db)
{
    passwd = (char*)"guest";
    return (DbHandle)
        mysql_real_connect (sql, host, user, passwd, db, 0, 0x0, 0);  //Noncompliant 
}

In this example, the arguments host (host name), user (user name), and passwd (password) are string literals and directly exposed in the code.

Querying the generated binary for ASCII strings can reveal this information.

Correction – Read Sensitive Data from Secured Configuration Files

One possible correction is to read the data from a configuration file. In the following corrected example, the call to function connect_to_database_server_init presumably reads the host name, user name, and password into its arguments from a secured configuration file.

// Typically, you include the header "mysql.h" with function and type declarations.
// In this example, only the required lines from the header are quoted.

typedef struct _MYSQL MYSQL;

MYSQL *mysql_real_connect(MYSQL *mysql,
                          const char *host, const char *user, const char *passwd,
                          const char *db, unsigned int port, const char *unix_socket,
                          unsigned long client_flag);

typedef void * DbHandle;
extern MYSQL *sql;

// File that uses functions from "mysql.h" 

int connect_to_database_server_init(const char **host,
                                    const char **user,
                                    const char **passwd,
                                    const char **db);

DbHandle connect_to_database_server(const char *db)
{
    const char *host_from_cfg;
    const char *user_from_cfg;
    const char *passwd_from_cfg;
    const char *db_from_cfg;
    if (connect_to_database_server_init(&host_from_cfg,
                                        &user_from_cfg,
                                        &passwd_from_cfg,
                                        &db_from_cfg))
    {
        return (DbHandle)
            mysql_real_connect (sql, host_from_cfg, user_from_cfg, 
                        passwd_from_cfg, db_from_cfg,  0, 0x0, 0);
    }
    else
        return (DbHandle)0x0;
}

Check Information

Category: Bad Coding Practices

Version History

Introduced in R2023a