Every one that have years working at the security ITC area know that so many security breach are caused by stack overflow, out of range, malformed parameters and NULL pointer exceptions to mention the more commons; this unhanded errors open gaps that interrupt the program’s flow leaving it in an undetermined state, and this is the factor used by hackers to insert tampered instructions in the programs’ flow in order to gain access to resources that is not supposed to have.
To mitigate this breach is necessary to implement a Defensive Programming structure in our code from the project’s beginning, follow and enforce the defensive rules again all the development process and allow ‘out of the box’ beta testing in order to stress the code.
At the project’s beginning is necessary to determine if the project’s coding needs to be split in modules and the communication between modules and with the users must be done using well defined interfaces. A well-defined interface need to specify number and type of parameter, max and minimum length of each one, format and expected values and border cases; if the transmission medium used by the interface can be tampered, for example a memory block or a pipe used during an Inter Process Communication (IPC) a checksum algorithm must use in order to verify the data integrity at least, or when is a remote communication or the Internet the media used a strong encryption algorithms like AES or CuaimaCrypt must be used in conjunction with the checksum algorithm in order to protect the data.
IPC must implement a message identification and delivery confirmation protocol, for example the originating process generate a random ID to each message sent and keep a record of it, the operation requested and the send time-stamp in a transactions list; the receiving process must return the result or a delivery confirmation, if the message don’t have a return value, to each message using the same ID generated by the originating process; when the originator process received the response message, must verify the ID and the operation type in the transaction list to find a match with the messages sent by it, if the received message was verified then is processed and the record is removed from the transaction list, in case the verification fail the message is discarded, the record is kept in the transaction list and the event is registered in the log. The originating process must check the transaction list for messages with no respond after a specified time out and record it at the log; how to treat the messages with an expired time out depend of the best strategy based on the applications requirement, can be discard it or resend it for example.
With this technique the IPC process is more resilient to fake responses and tampered messages, also always is necessary to include additional code in order to implement an authentication protocol in order to verify each partner at the IPC process is who say that it is.
During the development process is necessary to enforce the following of several safe programming techniques and use try/catch/finally blocks and assert in the source code.
The try/catch/finally block are used to handle exceptions and must be used to every block where an exception can occur and use it to allow the program respond to unforeseen situations and avoid unexpected behaviors as shown at the next Java example.
public class TryCatch_Test {
private static final long LigthSpeed = 299792458; // m/s
private static long calcShipSpeed() {
long speed = 1000000000000L;
try {
speed /= 0;
} catch (ArithmeticException e) {
System.out.println("Error: Don't divide a number by zero");
speed = LigthSpeed;
}
return speed;
}
public static void main(String[] args) {
long shipSpeed;
shipSpeed = calcShipSpeed();
System.out.println("Ship speed= " + shipSpeed);
}
}
The asserts are used during development and are to verify assumptions about the code result, for example if you are calculating a spaceship speed you can use the assert to verify if the resulted speed is less that the light speed as shown at the next example.
public class Assert_Test {
private static final double LigthSpeed = 299792458; // m/s
private static double calcShipSpeed(){
double speed = 1E12;
return speed;
}
public static void main(String[] args) {
double shipSpeed;
shipSpeed = calcShipSpeed();
assert shipSpeed<LigthSpeed;
System.out.println("Ship speed= "+shipSpeed);
}
}
In Java to enable the assert the –ea option must be used when the virtual machine is called.
Pointers, buffers and arrays are places where developers always seem to get themselves into trouble because is really easy to accidental reference to a NULL or get outside the assigned bounds. Defensive programmers should be checking the validity of a pointer before referencing it. Always check if is the pointer NULL and if it is don’t reference it; also verify if is the value stored in the pointer a valid value, If so then use it with confidence.
The Stack Over Flow error is one of the most common errors in recursive codes and these parameters are usually specified based in the worst-case scenario and a try and fail adjustment, but neither technique is sufficient and the worst-case situation that the stack will overflow becomes a real possibility
Most real-time operating systems (RTOS) have a stack monitor built into them and enabling the stack monitor is nothing more than adjusting a initialization macro with the configuration for the RTOS, but many OS don’t have this option available and the developer need to be more proactive handling this errors.
The options available to solve this possible situation depend of the develop language used, for example in C or C++ you can use a stack guard region filled with a known pattern and run a background task or thread to check the pattern integrity; once the stack monitor is able to detect the overflow additional application code is necessary to decide what to do with that information.
In Java the stack overflow is handled using the StackOverflowError
event using a try/catch block as show in the next example:
public class StackOverflow_Test {
public static int endless(int i) {
int value;
i++;
System.out.println("Deep Value:" + Integer.toString(i));
try {
value = endless(i);
} catch (StackOverflowError t) {
System.out.println("\nCaught StackOverflow Error");
value = i;
}
return value;
}
public static void main(String args[]) {
int A;
A = endless(0);
System.out.println("A=" + Integer.toString(A));
}
}
This code is just an example how to handle a stack over flow to keep a restraint state in the program and preserve a valid value to finish the execution in a controlled way.
In java is possible to control the stack size using the –Xss option when the virtual machine is called.
The last thing but not less important is keep an extensive events log, because in case of something going wrong this registers are the “program’s black-box” that will help you to know what happen and to solve the error in an efficient manner.
Performing a defensive programming will decrease the efficiency of the software having the application in a constant checking of the border cases but this performance hit is hardly noticeable on a modern system.
When creating a reliable system these types of checks need to be put into place in order to ensure that the system is operating as expected. In the event of a catastrophic failure such as a stack overflow, the system will be able to detect the events and take corrective actions before something terrible happens to the system or even worse a user of the system.
Julian Bolivar-Galeno is an Information and Communications Technologies (ICT) Architect whose expertise is in telecommunications, security and embedded systems. He works in BolivarTech focused on decision making, leadership, management and execution of projects oriented to develop strong security algorithms, artificial intelligence (AI) research and its applicability to smart solutions at mobile and embedded technologies, always producing resilient and innovative applications.