Hi Russ,
Post by Russ AllberyPost by Jeffrey WaltonIf a project does not observe proper preprocessor macros for a
configuration, a project could fall victim to runtime assertions and
actually DoS itself after the assert calls abort(). The ISC's DNS server
comes to mind (confer: there are CVE's assigned for the errant behavior,
and its happened more than once!
http://www.google.com/#q=isc+dns+assert+dos).
It's very rare for it to be sane to continue after an assert(). That
would normally mean a serious coding error on the part of the person who
wrote the assert(). The whole point of assert() is to establish
invariants which, if violated, would result in undefined behavior.
Continuing after an assert() could well lead to an even worse security
problem, such as a remote system compromise.
So, I somewhat disagree with you here. I think the differences are
philosophical because I could never find guidance from standard bodies
(such as Posix or IEEE) on rationales or goals behind NDEBUG and the
intention of the abort() behind an assert().
First, an observation: if all the use cases are accounted (positive
and negative), code *lacking* NDEBUG will never fire an asserts. The
default case of 'fail' is enough to ensure this. You would be
surprised (or maybe not) how many functions don't have the default
'fail' case. Any code that lacks NDEBUG because it depends upon
assert() the abort() is defective by design. That includes the ISC's
DNS server and their assertion/abort scheme (critical infrastructure,
no less).
Under no circumstance is a program allowed to abort(). It processes as
expected or it fails gracefully. If it fails gracefully, it can exit()
if it likes. But it does not crash, and it does not abort().
Here's the philosophical difference (that will surely draw criticism):
asserts are a debug/diagnostic tool to aide in development. They have
no place in release code. I'll take it a step further: Posix asserts
are useless during development under a debugger because the eventually
lead to SIGTERM. A much better approach in practice is to SIGTRAP.
Code under my purview must (1) validate all parameters and (2) check
all return values. Not only must there be logic to fail the function
if anything goes wrong, *everything* must be asserted to alert of the
point of first failure. In this respect, asserts create self-debugging
code.
I found developers did not like assert in debug configurations. They
did not like asserts because of SIGTERM, which meant the developers
did not fully assert. That caused the code to be non-compliant. The
root cause was they did not like eating the dogfood of their own bugs.
So I had to rewrite the asserts to use SIGTRAP, which made them very
happy (they could make a mental note and continue on debugging). Code
improved dramatically after that - we were always aware of the first
point of failure, with out the need for breakpoints and detailed
inspection unless needed.
Post by Russ AllberyThe purpose of the -DNDEBUG compile-time option is not to achieve
additional security by preventing a DoS, but rather to gain additional
*performance* by removing all the checks done via assert(). If your goal
is to favor security over performance, you never want to use -DNDEBUG.
Probably another philosophical difference: (1) code must be correct.
(2) code should be secure. (3) code can be efficient. NDEBUG just
removes the debugging/diagnostic aides, so it does help with (3). (1)
is achieved because there is a separate if/then/else that handles the
proper failure of a function in a release configuration.
I know many will disagree, but I will put my money where my mouth is:
I have code in the field (secure containers and secure channels) that
has never taken a bug report or taken less than a handful (fewer than
3). They were developed with the discipline described above, and they
include a complete suite of negative, multi-threaded self tests that
ensure graceful failures. I don't care too much about the positive
test cases since I can hire a kid from a third world country for $10
or $15 US a day to copy/paste code that works under the 'good' cases.
Can anyone else say claim have a non-trivial code base that does not
suffer defects (with a reasonable but broad definition of defect)?
Anyway, sorry about the philosophicals. I know it does not lend much
to the thread.
Jeff