Using enumerations

Enumerated types, also called enumerations, in C and C++ can greatly increase the readability and maintainability of source code. However, common coding errors that are associated with the use of enumerations can lead to security issues.

Recommendations

You can use the following guidelines to help reduce the risk of security issues when using enumerated types:

  • Remember that enumerated types in C and C++ might be signed or unsigned depending on the compiler.
  • Remember that any integer value is valid, not just those defined by a symbol.
  • Check the compiler documentation for command-line switches that might change the way enumerated types are handled.
  • When checking that a value is in a legal range, make sure that there is both an upper and lower bounds check.
  • When using switch statements, make sure that there is a default clause.
  • When using switch statements, make sure that variables set by some cases are initialized before the switch.
  • Where possible, use enumerated types for a single contiguous range of values.
  • When using multiple discrete values, consider using #define rather than enum.

Enumerated types

Enumerated types assign constant integer values to symbols. According to the C standard, the expression that defines the value of an enumeration constant shall be an integer constant expression that has a value representable as an int. However, the implementation is compiler-dependent, so the same source compiled with two different compilers could behave differently. The QNX QCC and GCC compilers use unsigned values by default. If you are developing apps for multiple platforms and using other compilers, you should check the compiler documentation for implementation-specific information and keep in mind the unsigned nature of default enumerated values.

If a compiler uses unsigned integers by default, specifying a negative value will cause the compiler to use signed integers. However, the converse isn't true. If a compiler uses signed integers, it's not possible to create an unsigned enumeration by, for example, setting one element of the enumeration to a value (such as 0xffffffff) that's too large for a signed integer.

To demonstrate this, consider the following example:

#include <stdio.h>

int main(void) {    
     enum Test1 { a, b, c };    
     enum Test2 { x = 1, y = 65536, z = 0xffffffff };

     printf("a = %d, b = %d, c = %d\n", a, b, c); 
                           
     if (z < 0)
         printf("Signed: x = %d, y = %d, z = %d\n", x, y, z);
     else 
         printf("Unsigned: x = %u, y = %u, z =  %u\n", x, y, z);
     return 0;
}

When this code is compiled with the GCC and run, the following output is produced:

a = 0, b = 1, c = 2
Signed: x = 1, y = 65536, z = 4294967295

Because enumerated types are integers, the guidance provided in Using integers applies. While arithmetic isn't particularly common with enumerated types, arithmetic is legal code and is often used in bounds checking.