The C Standard, 7.1.4 paragraph 1, [ISO/IEC 9899:2024] states
Any function declared in a header may be additionally implemented as a function-like macro defined in the header, so if a library function is declared explicitly when its header is included, one of the techniques shown later in the next subclause can be used to ensure the declaration is not affected by such a macro. Any macro definition of a function can be suppressed locally by enclosing the name of the function in parentheses, because the name is then not followed by the left parenthesis that indicates expansion of a macro function name. For the same syntactic reason, it is permitted to take the address of a library function even if it is also defined as a macro. 220) The use of #undef to remove any macro definition will also ensure that an actual function is referred to.
220)This means that an implementation is required to provide an actual function for each library function, even if it also provides a macro for that function.
However, the C Standard enumerates specific exceptions in which the behavior of accessing an object or function expanded to be a standard library macro definition is undefined. The macros are assert
, errno
, math_errhandling
, setjmp
, va_arg
, va_copy
, va_end
, and va_start
. These cases are described by undefined behaviors 110, 114, 122, 124, and 138. Programmers must not suppress these macros to access the underlying object or function.
Noncompliant Code Example (assert
)
In this noncompliant code example, the standard assert()
macro is suppressed in an attempt to pass it as a function pointer to the execute_handler()
function. Attempting to suppress the assert()
macro is undefined behavior.
#include <assert.h> typedef void (*handler_type)(int); void execute_handler(handler_type handler, int value) { handler(value); } void func(int e) { execute_handler(&(assert), e < 0); }
Compliant Solution (assert
)
In this compliant solution, the assert()
macro is wrapped in a helper function, removing the undefined behavior:
#include <assert.h> typedef void (*handler_type)(int); void execute_handler(handler_type handler, int value) { handler(value); } static void assert_handler(int value) { assert(value); } void func(int e) { execute_handler(&assert_handler, e < 0); }
Noncompliant Code Example (Redefining errno
)
Legacy code is apt to include an incorrect declaration, such as the following in this noncompliant code example:
extern int errno;
Compliant Solution (Declaring errno
)
This compliant solution demonstrates the correct way to declare errno
by including the header <errno.h>
:
#include <errno.h>
C-conforming implementations are required to declare errno
in <errno.h>
, although some historic implementations failed to do so.
Risk Assessment
Accessing objects or functions underlying the specific macros enumerated in this rule is undefined behavior.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
MSC38-C | Low | Unlikely | Medium | P2 | L3 |
Automated Detection
Tool | Version | Checker | Description |
---|---|---|---|
Astrée | 24.04 | Supported, but no explicit checker | |
CodeSonar | 8.1p0 | BADMACRO.STDARG_H | Use of <stdarg.h> Feature |
Cppcheck Premium | 24.9.0 | premium-cert-msc38-c | Fully implemented |
Helix QAC | 2024.3 | C3437, C3475 C++3127, C++5039 | |
Parasoft C/C++test | 2023.1 | CERT_C-MSC38-a | A function-like macro shall not be invoked without all of its arguments |
R2024a | CERT C: Rule MSC38-C | Checks for predefined macro used as an object (rule fully covered) | |
RuleChecker | 24.04 | Supported, but no explicit checker |
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 C | DCL37-C. Do not declare or define a reserved identifier | Prior to 2018-01-12: CERT: Unspecified Relationship |
Bibliography
ISO/IEC 9899:2024 | 7.1.4, "Use of Library Functions" |
16 Comments
David Svoboda
My current feeling is that this rule can remain distinct from DCL37-C, as that rule specifically deals with classes of reserved identifiers (eg ids that start with _). This rule should swallow ERR31-C.
A couple of comments on your assert NCCE/CS:
Martin Sebor
There are two preconditions for undefined behavior 104:
assert()
macro definitions is suppressedassert()
is accessedThe NCCE meets the first precondition but since the code never references an
assert()
function, it doesn't necessarily exhibit undefined behavior. (Would the behavior of the example change if the#undef
directive were removed? If not, it's probably not a valid NCCE.)FWIW, I think the undefined behavior applies to something like the following:
Frank Costello
Ah, I thought the standard meant referring to the function underlying the assert macro, but looking at it again, it does seem to mean using a function called assert.
But then, wouldn't your code above necessarily not compile because the assert function identifier is undefined? I feel like ub14 would be triggered by something like this:
Martin Sebor
C allows calls to functions without function prototypes in scope so the NCCE I gave would compile but (most likely) fail to link. Failing to link is one possible manifestation of undefined behavior. The closest match I can find in Annex J is UB 38.
Your example above doesn't have undefined behavior per se because you defined
assert(int)
. The code simply calls the user-definedassert()
function, just as it would if<assert.h>
hadn't been included (and assuming no other definition of theassert()
function existed in the program). That's well-defined.I should add: if there is another definition of
assert()
in the program, your program would certainly have undefined behavior – UB 36. But the C standard doesn't allow implementations to define such a function:assert()
must be a macro.David Svoboda
The assert NCCE looks informative, but the contents of assert.h can be described in prose (rather than code comments) something like "defines an assert() macro and does not define an assert() function". Also it needs to define a (user-suplised?) assert() function. Consequently the CS should declare a myassert() function, and it (obviously) can't come from <assert.h>
Martin Sebor
The first NCCE may exemplify highly questionable practice but it doesn't have undefined behavior because it includes
<myassert.h>
which defines a function namedassert()
. Suppressing theassert
macro infullAssert()
will just end up calling this function. As I said before, the problem is when no such function exists (e.g., as in the example I gave in my comment above).David Svoboda
C99 (n1256), S7.2 says:
As I see it, the question of whether the 1st NCCE invokes undefined behavior is open to interpretation. Does the text "in order to access an actual function" imply that
1. the dveloper thinks the assert() macro is covering an implementation-defined assert function, and they wish to access the function w/o using the macro?
or
2. the developer creates their own assert() function and suppresses the assert() macro in order to access their own function.
The 1st NCCE falls under case (2), and I suspect (but am not sure that) the standard meant case (1).
Martin Sebor
C99 makes a distinction between suppressing a macro (which is explicitly allowed except in a few cases such as
assert
anderrno
) and defining an identifier with the same name (which is disallowed in general except when C99 doesn't define the identifier to have linkage). This difference is exemplified in the specification ofassert
anderrno
(and a handful of other cases). Forassert
:For
errno
:It's certainly possible that this distinction is unintended. Let's see what the response is from WG14.
Frank Costello
Certainly a subtle point, but it looks like we finally have a consensus.
Defining and using an object named assert (e.g.
int assert
) is conforming, even if it suppresses the standardassert()
macro to access that object. Defining and using a functionassert()
is conforming in a translation unit that does not include<assert.h>
. However, if the standard assert header is included, suppressing theassert()
macro to access a function is not strictly conforming. (This is a summary of information from Derek Jones.)So, the current assert NCCE above does demonstrate undefined behavior.
Martin Sebor
I concede that the feedback we got from WG14 does indeed put the NCCE in the realm of undefined behavior, even though no one was able to produce a plausible example of effects other than calling the user-defined
assert()
function. That being said, I certainly agree that defining a functionassert()
is a bad idea regardless of whether<assert.h>
is included and should be strongly discouraged, if only because it is confusing.David Svoboda
This is a good rule, but I'm not impressed with the title...its terribly vague. A better title would be something along the lines of:
Don't redefine assert, errno, or any other object or function that might be a macro.
I realize this is, strictly speaking, less accurate than the currnet title, but it is more indicative of what the rule is about. IMHO accuracy is more suited for the intro paragraph than the title. Comments?
Martin Sebor
The problem this guideline attempts to warn against is accessing (not just redefining) the symbols underlying the handful of identifiers that may only be implemented as macros (and not necessarily as functions/objects with the same name). The problem, in all cases, is the same as the one mentioned in my comment Re: MSC38-C. Do not treat as an object any predefined identifier that might be implemented as a macro: that the underlying object or function may not exist. The effect of accessing it is a compilation or linker error. It is a compilation when no matching declaration exists, and it is a linker error when a matching declaration exists for which the program (or the C library) does not provide a definition.
An example involving
errno
is:Note that neither NCCE in this guideline demonstrates the problem the guideline is about. They both declare an identifier with the same name as one of the reserved names (which may be a bad idea but it will not cause a problem on any implementation).
As I have been trying to say all along, the noncompliant examples should demonstrate the real risks of violating the language rules and they should be reproducible with popular compilers. The NCCE involving
errno
I give above clearly illustrates the problem when compiled with gcc on Linux:The
assert
example I gave in my earlier comments produces similar output:A slightly modified example involving
errno
that demonstrates a linker error looks like this:Compiling and linking it with gcc on Linux/x64 produces the following output:
David Svoboda
The NCCEs both demonstrate undefined behavior, which has traditionally been enough to categorize them as noncompliant. (even if your implementation handles them benignly, such as by refusing to compile the code.) Furthermore, code that always produces a compiler error is not, in our experience, a valid NCCE, because such code would never escape a developer team or QA...it is code that compilers compile that you must watch out for.
Examples of code that produce compiler or linker errors is not particularly scary. If you can show a NCCE that compiles into a program that does something unexpected, that would be a worthy implementation detail for this rule.
However, you're right that the rule is about accessing macro-hidden objects rather than redefining them. I don't have a good alternate title for this rule...perhaps this one?
Don't treat as an object any predefined identifier that might be implemented as a macro
Martin Sebor
The examples exhibit undefined behavior only in theory, and only because that's what the experts insisted on. In reality, they are benign on all implementations that I am aware of. I.e., there is no risk that this will lead to any kind of scary effects on any existing implementation:
I agree that compiler or linker errors are much less interesting from the point of view of exploits than other cases of undefined behavior (those that manifest themselves at program runtime). Unfortunately (for the purposes of our efforts to come up with such examples), there is nothing scary about accessing the functions or objects underlying the definitions of
assert()
orerrno
, or even redefining them. The scariest undefined behavior you can hope to find is the linker error.Aaron Ballman
The way the title reads, to me, is incorrect. The standard explicitly says that you can suppress library-defined function macros in the general case (7.1.4p1):
The title seems to prohibit this behavior generally instead of in the few specific cases outlined in the rule. However, I'm at a loss for an improved title.
Aaron Ballman
I couldn't figure out a better name for a title, but posed the question internally. However, I did clarify the contents of the rule and update the
assert
code example to be a bit more compelling (hopefully).