Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Updated references from C11->C23

Background

While it used to be common practice to Although programmers often use integers and pointers interchangeably in C (although it was not considered good style), the C99 standard has mandated that pointer-to-integer and integer-to-pointer conversions are implementation defined. This means that they are not necessarily portable from one system to the next, and additionally multiple conversions may or may not give the desired behavior.

This means that pointer to integer conversions should generally be avoided, and special care should be taken with NULL and 0 pointers. This recommendation details the proper code for both conversions and null/integer behavior. 

Part 1: NULL and 0

If the compiler can successfully identify a '0' in code as being assigned to pointer, then it will convert it to a NULL pointer. This applies to most pointer operations, but it does not apply to particular function calls that do not explicitly specify they will take a pointer. This may lead to a pointer to integer cast, which, as described above, is a bad idea in general, and in this case not even intended.

Non Compliant Code:

Code Block

execl("/bin/sh", "sh", "-c", "ls", 0);

Code example from the old comp.lang.faq on null pointers (linked below).

From the execl man page for Fedora Linux:

"int execl(const char *path, const char *arg, ...);
...The  const char *arg and subsequent ellipses in the execl, execlp, and
execle functions can be thought of as arg0, arg1, ..., argn.  Together
they  describe  a  list  of  one  or  more pointers to null-terminated
strings that represent the argument list  available  to  the  executed
program.   The first argument, by convention, should point to the file
name associated with the file being executed.  The list  of  arguments
must be terminated by a NULL pointer."

Therefore, because the type of the '0' argument is not explicitly a pointer, it will be cast to an integer. It is thusly necessary to force 0 to be convert to a pointer by casting.

Compliant Code

Code Block

execl("/bin/sh", "sh", "-c", "ls", (char *)0);

Because of the cast, this will pass a null pointer. This represents the end of the list of arguments. 

 Note: Using NULL in place of the 0 will not change the need to cast. The preprocessor will convert the NULLS to 0's and use the same rules for pointer casting as described above.

Part 2: Universal Integer/Pointer Storage

It is recommended to use a union if you need to memory to be accessible as both a pointer and an integer rather than make the cast. Since a union is the size of the largest element and will faithfully represent both as the implementation defines, it will ensure the proper behavior and keep data from being lost.

Non-Compliant Code

Code Block

unsigned int * chick = 0xcafebabe;
...
unsigned int hotchick = chick+1;
unsigned int * blinddate = chick;

Compliant Code

Code Block

union intpoint {
	unsigned int * pointer;
	unsigned int number;
} intpoint;
...
intpoint mydata = 0xcafebabe;
...
unsigned int hotchick = mydata.number+1;
unsigned int * blinddate = mydata.pointer;

The non-compliant code leads to many possible conversion errors and additionally overflow. All of these are avoided by using a union. 

Part 3: Accessing Memory-Mapped Addresses

Note: This is a bad idea. The following is a description of how to properly execute a bad idea.

Often times in low level, kernel, and graphics code you will need to access memory at a specific locations. This means you are going to have to make a literal integer to pointer to conversion! This will make your code non-portable!

The trick here is to convince the compiler you are going to do something bad instead of just assigning a pointer to an integer.

Non-Compliant Code

Code Block

unsigned int * happyplace = 0xcafebabe;

Because integers are no longer pointers this could have drastic consequences.

Compliant Code

Code Block

unsigned int * happyplace = (unsigned int *) 0xcafebabe;

The trick here is you are telling the compiler you really do want to do this bad thing. If on your system pointers are typecast to something other than an unsigned int, then you want to use the type they are defined to be instead of unsigned int.

Credits:

- A lot of my ideas and information on NULL and 0 came from the old comp.lang.c FAQ. Read it here

- I used this little tutorial to help me figure out the particulars of unions, as they are not often used. Read it here

-defined

Conversions between integers and pointers can have undesired consequences depending on the implementation.

According to the C Standard, subclause 6.3.2.3 [ISO/IEC 9899:2024],

An integer may be converted to any pointer type. Except as previously specified, the result is implementation-defined, might not be correctly aligned, might not point to an entity of the referenced type, and might be a trap representation.

Any pointer type may be converted to an integer type. Except as previously specified, the result is implementation-defined. If the result cannot be represented in the integer type, the behavior is undefined. The result need not be in the range of values of any integer type.

Do not convert an integer type to a pointer type if the resulting pointer is incorrectly aligned, does not point to an entity of the referenced type, or is a trap representation.

Do not convert a pointer type to an integer type if the result cannot be represented in the integer type. (See undefined behavior 24.)

The mapping between pointers and integers must be consistent with the addressing structure of the execution environment. Issues may arise, for example, on architectures that have a segmented memory model.

Noncompliant Code Example

The size of a pointer can be greater than the size of an integer, such as in an implementation where pointers are 64 bits and unsigned integers are 32 bits. This code example is noncompliant on such implementations because the result of converting the 64-bit ptr cannot be represented in the 32-bit integer type:

Code Block
bgColor#ffcccc
langc
void f(void) {
  char *ptr;
  /* ... */
  unsigned int number = (unsigned int)ptr;
  /* ... */
}

Compliant Solution

Any valid pointer to void can be converted to intptr_t or uintptr_t and back with no change in value. (See INT36-EX2.) The C Standard guarantees that a pointer to void may be converted to or from a pointer to any object type and back again and that the result must compare equal to the original pointer. Consequently, converting directly from a char * pointer to a uintptr_t, as in this compliant solution, is allowed on implementations that support the uintptr_t type.

Code Block
bgColor#ccccff
langc
#include <stdint.h>
 
void f(void) {
  char *ptr;
  /* ... */
  uintptr_t number = (uintptr_t)ptr;  
  /* ... */
}

Noncompliant Code Example

In this noncompliant code example, the pointer ptr is converted to an integer value. The high-order 9 bits of the number are used to hold a flag value, and the result is converted back into a pointer. This example is noncompliant on an implementation where pointers are 64 bits and unsigned integers are 32 bits because the result of converting the 64-bit ptr cannot be represented in the 32-bit integer type.

Code Block
bgColor#ffcccc
langc
void func(unsigned int flag) {
  char *ptr;
  /* ... */
  unsigned int number = (unsigned int)ptr;
  number = (number & 0x7fffff) | (flag << 23);
  ptr = (char *)number;
}

A similar scheme was used in early versions of Emacs, limiting its portability and preventing the ability to edit files larger than 8MB.

Compliant Solution

This compliant solution uses a struct to provide storage for both the pointer and the flag value. This solution is portable to machines of different word sizes, both smaller and larger than 32 bits, working even when pointers cannot be represented in any integer type. 

Code Block
bgColor#ccccff
langc
struct ptrflag {
  char *pointer;
  unsigned int flag : 9;
} ptrflag;
 
void func(unsigned int flag) {
  char *ptr;
  /* ... */
  ptrflag.pointer = ptr;
  ptrflag.flag = flag;
}

Noncompliant Code Example

It is sometimes necessary to access memory at a specific location, requiring a literal integer to pointer conversion. In this noncompliant code, a pointer is set directly to an integer constant, where it is unknown whether the result will be as intended:

Code Block
bgColor#FFcccc
langc
unsigned int *g(void) {
  unsigned int *ptr = 0xdeadbeef;
  /* ... */
  return ptr;
} 

The result of this assignment is implementation-defined, might not be correctly aligned, might not point to an entity of the referenced type, and might be a trap representation.

Compliant Solution

Unfortunately this code cannot be made safe while strictly conforming to ISO C.

A particular platform (that is, hardware, operating system, compiler, and Standard C library) might guarantee that a memory address is correctly aligned for the pointer type, and actually contains a value for that type. A common practice is to use addresses that are known to point to hardware that provides valid values.

Exceptions

INT36-C-EX1: The integer value 0 can be converted to a pointer; it becomes the null pointer.

INT36-C-EX2: Any valid pointer to void can be converted to intptr_t or uintptr_t or their underlying types and back again with no change in value. Use of underlying types instead of intptr_t or uintptr_t is discouraged, however, because it limits portability.

Code Block
bgColor#ccccff
langc
#include <assert.h>
#include <stdint.h>
 
void h(void) {
  intptr_t i = (intptr_t)(void *)&i;
  uintptr_t j = (uintptr_t)(void *)&j;
 
  void *ip = (void *)i;
  void *jp = (void *)j;
 
  assert(ip == &i);
  assert(jp == &j);
}

Risk Assessment

Converting from pointer to integer or vice versa results in code that is not portable and may create unexpected pointers to invalid memory locations.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

INT36-C

Low

Probable

High

P2

L3

Automated Detection

Tool

Version

Checker

Description

Astrée
Include Page
Astrée_V
Astrée_V

pointer-integral-cast

pointer-integral-cast-implicit

function-pointer-integer-cast

function-pointer-integer-cast-implicit

Fully checked
Axivion Bauhaus Suite

Include Page
Axivion Bauhaus Suite_V
Axivion Bauhaus Suite_V

CertC-INT36Fully implemented
Clang
Include Page
Clang_V
Clang_V
-Wint-to-pointer-cast, -Wint-conversionCan detect some instances of this rule, but does not detect all
CodeSonar
Include Page
CodeSonar_V
CodeSonar_V
LANG.CAST.PC.CONST2PTR
LANG.CAST.PC.INT
Conversion: integer constant to pointer
Conversion: pointer/integer
Compass/ROSE


Coverity
Include Page
Coverity_V
Coverity_V
PW.POINTER_CONVERSION_LOSES_BITSFully implemented
Cppcheck Premium

Include Page
Cppcheck Premium_V
Cppcheck Premium_V

premium-cert-int36-cPartially implemented
Helix QAC

Include Page
Helix QAC_V
Helix QAC_V

C0303, C0305, C0306, C0309, C0324, C0326, C0360, C0361, C0362

C++3040, C++3041, C++3042, C++3043, C++3044, C++3045, C++3046, C++3047, C++3048


Klocwork
Include Page
Klocwork_V
Klocwork_V
MISRA.CAST.OBJ_PTR_TO_INT.2012
LDRA tool suite
Include Page
LDRA_V
LDRA_V

439 S, 440 S

Fully implemented
Parasoft C/C++test
Include Page
Parasoft_V
Parasoft_V

CERT_C-INT36-b

A conversion should not be performed between a pointer to object type and an integer type other than 'uintptr_t' or 'intptr_t'

PC-lint Plus

Include Page
PC-lint Plus_V
PC-lint Plus_V

4287

Partially supported: reports casts from pointer types to smaller integer types which lose information

Polyspace Bug Finder

Include Page
Polyspace Bug Finder_V
Polyspace Bug Finder_V

CERT C: Rule INT36-C


Checks for unsafe conversion between pointer and integer (rule partially covered)

PVS-Studio

Include Page
PVS-Studio_V
PVS-Studio_V

V527, V528V542, V566, V601V647, V1091
RuleChecker
Include Page
RuleChecker_V
RuleChecker_V

pointer-integral-cast

pointer-integral-cast-implicit

function-pointer-integer-cast

function-pointer-integer-cast-implicit

Fully checked
SonarQube C/C++ Plugin
Include Page
SonarQube C/C++ Plugin_V
SonarQube C/C++ Plugin_V
S1767Partially implemented

Related Vulnerabilities

Search for vulnerabilities resulting from the violation of this rule on the CERT website.

Related Guidelines

Key here (explains table format and definitions)

Taxonomy

Taxonomy item

Relationship

CERT CINT11-CPP. Take care when converting from pointer to integer or integer to pointerPrior to 2018-01-12: CERT: Unspecified Relationship
ISO/IEC TR 24772:2013Pointer Casting and Pointer Type Changes [HFC]Prior to 2018-01-12: CERT: Unspecified Relationship
ISO/IEC TS 17961:2013Converting a pointer to integer or integer to pointer [intptrconv]Prior to 2018-01-12: CERT: Unspecified Relationship
CWE 2.11CWE-587, Assignment of a Fixed Address to a Pointer2017-07-07: CERT: Partial overlap
CWE 2.11CWE-7042017-06-14: CERT: Rule subset of CWE
CWE 2.11CWE-7582017-07-07: CERT: Rule subset of CWE
CWE 3.1CWE-119, Improper Restriction of Operations within the Bounds of a Memory Buffer2018-10-19:CERT:None
CWE 3.1CWE-466, Return of Pointer Value Outside of Expected Range2018-10-19:CERT:None

CERT-CWE Mapping Notes

Key here for mapping notes

CWE-758 and INT36-C

Independent( INT34-C, INT36-C, MEM30-C, MSC37-C, FLP32-C, EXP33-C, EXP30-C, ERR34-C, ARR32-C)

CWE-758 = Union( INT36-C, list) where list =

  • Undefined behavior that results from anything other than integer <-> pointer conversion

CWE-704 and INT36-C

CWE-704 = Union( INT36-C, list) where list =

  • Incorrect (?) typecast that is not between integers and pointers

CWE-587 and INT36-C

Intersection( CWE-587, INT36-C) =

  • Setting a pointer to an integer value that is ill-defined (trap representation, improperly aligned, mis-typed, etc)

CWE-587 – INT36-C =

  • Setting a pointer to a valid integer value (eg points to an object of the correct t ype)

INT36-C – CWE-587 =

  • Illegal pointer-to-integer conversion

Intersection(INT36-C,CWE-466) =  ∅  

Intersection(INT36-C,CWE-466) = 

An example explaining the above two equations follows:

static char x[3];

char* foo() {

  int x_int = (int) x; // x_int = 999 eg

  return x_int + 5; // returns 1004 , violates CWE 466

}

...

int y_int = foo(); // violates CWE-466

char* y = (char*) y_int; //  // well-defined but y may be invalid, violates INT36-C

char c = *y; // indeterminate value, out-of-bounds read, violates CWE-119

Bibliography

[ISO/IEC 9899:2024]6.3.2.3, "Pointers"


...

Image Added Image Added Image Added- Even more of my ideas came from the more recent comp.lang.c FAQ. Read it here