Integer constants are often used as masks or specific bit values. Frequently, these constants are expressed in hexadecimal form to communicate to the programmer how the data is represented in the machine. But this could lead to making assumptions about the system which might not hold true.
A hexadecimal constant without a suffix is defined as an unsigned int if it can be represented in 32 bits and if the high order bit is turned on. For example, the constant 0xFFFFFFFFL is a signed long on a 32-bit system (this sets all the bits to '1'). But on a 64-bit system, only the lower order 32-bits are set, resulting in the value 0x00000000FFFFFFFF. The following examples assume two's complement arithmetic.
Noncompliant Solution
The noncompliant solution attempts to take two's complement of a long using a mask. This solution works as intended when compiled and run on 32-bit machines, but its 64-bit equivalent binary will not run correctly.
#define MASK 0xFFFFFFFFL ... long x = -1L; /* should not do bitwise operations on signed numbers*/ unsigned long u_x = (unsigned long) x; unsigned long positive_u_x = (u_x ^ MASK) + 1; long positive_x = (signed long) positive_u_x;
Compliant Solution
If you want to turn all the bits on, a portable (and safer) way to do this is to define a constant with a value of ~0. This turns all the bits on regardless of the arithmetic:
#define MASK ~0 ... long x = -1L; /* should not do bitwise operations on signed numbers*/ unsigned long u_x = (unsigned long) x; unsigned long positive_u_x = (u_x ^ MASK) + 1; long positive_x = (signed long) positive_u_x;
Compliant Solution
Another compliant solution would be to use a constant, rather than a macro.
long MASK = ~0; ... long x = -1L; /*should not do bitwise operations on signed numbers*/ unsigned long u_x = (unsigned long) x; unsigned long positive_u_x = (u_x ^ MASK) + 1; long positive_x = (signed long) positive_u_x;
Constants can be explicitly typed, avoiding the int/long ambiguity described above. For more information comparing constants and macros, see DCL06-C. Use meaningful symbolic constants to represent literal values in program logic.
Noncompliant Solution
Another problem that might arise is the setting of the most significant bit. On a 32-bit system, the constant 0x80000000 is used:
#define MASK 0x80000000L ... long x = -1L; unsigned long u_x = (unsigned long) x; unsigned long ones_complement_u_x = (u_x ^ MASK); long ones_complement_x = (signed long) ones_complement_u_x;
Compliant Solution
A more portable way of doing this is to use a shift expression:
#define MASK (1L << ((sizeof(long) * CHAR_BIT) - 1)) ... long x = -1L; unsigned long u_x = (unsigned long) x; unsigned long ones_complement_u_x = (u_x ^ MASK); long ones_complement_x = (signed long) ones_complement_u_x;
Refer to INT02-C. Understand integer conversion rules for details about how integer conversion can affect security in other ways.
Risk Assessment
When porting code, there are high chances of new insecurities getting introduced. In the case of numeric constants used to define the size of input strings, there are chances that some buffer checks might get invalidated, which could create the potential for buffer overflow style attacks. If you know that you will have to port your code in the future to other data models, writing portable code (without making any assumptions about the data model) in the beginning is recommended.
Recommendation |
Severity |
Likelihood |
Remediation Cost |
Priority |
Level |
|---|---|---|---|---|---|
PRE12-C |
high |
probable |
low |
P6 |
L2 |
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Other Languages
This rule appears in the C++ Secure Coding Standard as PRE12-CPP. Define numeric constants in a portable way.
References
[[Dewhurst 02]] Gotcha #25, "#define Literals"
PRE11-C. Do not conclude a single statement macro definition with a semicolon 01. Preprocessor (PRE)