Source:
Why should you care?
Any secure system is always being rigorously tested by stresses and needs to be reinforced to withstand various challenges, including:
Attacks
Erroneous/Malicious Inputs
Hardware or Software Faults
Unanticipated User Behavior
Unexpected Environmental Changes
Vulnerabilities that can arise from improper error handling can include the following:
Denial of Service (DOS): Programs may crash or become unresponsive when encountering unexpected inputs or situations.
Resource Leaks: Memory, file handles, and network connections may leak and accumulate over time leading to performance issues or system crashes.
Data Corruption: Data can be inadvertently modified in an unintended way.
Information Disclosure: Improper error handling can lead to sensitive information being disclosed with error messages or memory dumps revealing information about the system.
Privilege Escalation: An attacker may exploit the improper handling of exceptional conditions to execute unauthorized actions.
How can I tell if my system is secure?
The overall dependability of a system can be measured with reliability, availability, safety, and security. Reliability refers to a system or component's capacity to consistently perform its designated functions. It can be compared to a durable wrench that has been used for years but shows minimal wear and tear. Effective systems exhibit this quality. Availability, on the other hand, concerns the system's accessibility and dependability for its intended users. If the same reliable wrench were frequently borrowed by a friend without notice, making it uncertain whether it would be accessible when needed, it may be reliable but not available.
Survivability is one component of reliability that is measured by the three R's. Resistance, recognition, and recovery. Resistance refers to the steps taken to fortify a system against specific stressors. For example, consider the type of metal used to create a wrench or whether it has a surface coating or oil to prevent rust. Recognition is the situational awareness regarding instances of stress and their impact on the system. It can be likened to an experienced mechanic who skillfully uses a wrench in various challenging situations. Recovery is the ability of a system to restore services during or after an attack, incident, or service disruption. Picture a wrench that can automatically repair itself when damaged or affected by external factors, returning to its original functional state.
Let's get practical - Error policy outline
ISO/IEC TR 24772:2013, section 6.39.5 [ISO/IEC TR 24772:2013], describes the following mitigation strategies:
Software developers can avoid the vulnerability or mitigate its ill effects in the following ways:
A strategy for fault handling should be decided. Consistency in fault handling should be the same with respect to critically similar parts.
A multi-tiered approach of fault prevention, fault detection, and fault reaction should be used.
System-defined components that assist in uniformity of fault handling should be used when available. For one example, designing a "runtime constraint handler" (as described in Annex K of [the C Standard]) permits the application to intercept various erroneous situations and perform one consistent response, such as flushing a previous transaction and restarting at the next one.
When there are multiple tasks, a fault-handling policy should be specified whereby a task may
halt, and keep its resources available for other tasks (perhaps permitting restarting of the faulting task)
halt, and remove its resources (perhaps to allow other tasks to use the resources so freed, or to allow a recreation of the task)
halt, and signal the rest of the program to likewise halt
Common error handling mistakes
Using in-band error indicators
In-band error indicators involve signaling errors or exceptional conditions through the same communication channel or data stream as regular data. This method integrates error information into the normal data flow instead of transmitting it via a separate channel or mechanism.
For example, using a return value of -1 to signal an error in functions that returns integers (such as file descriptors or array indices). In this case, -1 is an in-band error indicator because it is part of the same integer data type used for valid results.
They're convenient and simple! Why should I avoid them?
In-band error indicators are less reliable and may be confused with valid data. They create ambiguity since specific values must be reserved for error signaling, limiting the range of valid data. Additionally, they necessitate extra logic for detecting and managing errors, potentially complicating an otherwise straightforward function.
Ignoring values returned by functions
Functions typically return values in one of two ways: either by returning integers that represent the function's exit status, or by returning a value that is the result of a computation and thus integral to the function's API. If you plan to ignore a function, you should do so explicitly by casting the function to void.
int increment(int x) {
return x + 1;
}
int main(void) {
int a = 5;
(void) increment(a); // The return value of increment() is ignored, implicitly cast to void
...
}
If a return value is insignificant or if errors can be disregarded, such as for functions called solely for their side effects, the function should be explicitly defined as void to demonstrate the programmer's intent.
void increment(int *x) {
return *x + 1;
}
Using functions that don't support error checking
When choosing between two functions that achieve the same objective, opt for the one with superior error checking and reporting. This aligns with the practice of avoiding deprecated or obsolete functions. One effective method to ensure this is by creating a blacklist of functions that have better alternatives available.
Not watching for data from tainted sources
A tainted source refers to any external source of untrusted data. In the context of C programming, tainted sources include parameters to the main() function; return values from localeconv(), fgetc(), getc(), getchar(), fgetwc(), getwc(), and getwchar(); and strings produced by getenv(), fscanf(), vfscanf(), vscanf(), fgets(), fread(), fwscanf(), vfwscanf(), vwscanf(), wscanf(), and fgetws(). This is not an exhaustive list, input validation is essential when your system is interacting with any external source of untrusted data.