C is an inherently insecure programming language. For example, most of its standard string library functions can be used for buffer or format string attacks if not used carefully. However, because of its flexibility, speed, and relative ease of learning, it is a widely used programming language. Here are some specifications for developing secure C programs.
1.1.1 Buffer Overflow
Avoid string functions that do not perform boundary checking, as they can be used for buffer overflow attacks. Here are some functions to avoid. At the same time, the corresponding safe substitution method of each function is also listed.
Anyway, instead of using strcpy(), you use strncpy();
Anyway, instead of strcat(), you use strncat();
Anyway, you skip sprintf() and use snprintf() instead;
Anyway, instead of using gets(), you use fgets().
In the first three middle functions above, the “n” of each alternative function indicates the size of the buffer used. The “f” of the last function, which stands for format, allows the user to specify the format of the desired input. These substitution equations force the programmer to define the size of the buffer to use and determine the type of input.
1.1.2 Formatting String Attacks
These types of attacks tend to be related to buffer overflows because they primarily exploit the assumptions of certain functions, such as sprintf() and vsprintf(), which assume that the length of the buffer is infinite. However, even replacing sprintf() with snprintf() doesn’t completely protect your program from formatted strings. These attacks are made by passing formatspecifiers (%d, %s, %n, etc.) directly to the output function receive buffer.
For example, the following code is an unsafe snprintf(buffer,sizeof(buffer),string). In this case, a format specifier can be inserted into the string to manipulate the stack of memory and write the attacker’s data (which contains small program code and can then be executed by the processor). The following code is recommended for the above example.
Snprintf (buffer,sizeof(buffer), “%s”,string) format string attacks are not easy. An attacker must first be able to obtain the contents of the stack (either exported from an application or using a debugger), and then must know how to precisely access a particular memory space to manipulate variables in the stack.
Executing external programs It is recommended to use the exec() function instead of the system() function to execute external programs. This is because system() receives a random buffer of the entire command line to execute the program.
snprintf(buffer,sizeof(buffer),”emacs%s”,filename);
system(buffer);
In the example above, you can insert additional commands into sehll by using a semicolon on the file name variable (e.g., the file name can be /etc/hosts; Rm *, which will display the /etc/hosts directory files while deleting all files in the directory). The exec() function only guarantees that the first argument is executed:
execl(“usr/bin/emacs”,”usr/bin/emacs”,filename,NULL);
The example above ensures that the file name is entered only as a parameter to the Emacs tool, and also that it uses the full PATH in the Emacs command rather than the PATH environment variable that can be exploited by an attacker.
1.1.3 Competition Conditions
When a process needs to access a resource (whether disk, memory, or file), it usually performs two steps:
1. Test whether the resource is free and available.
2. Access the resource if it is available, otherwise it waits until it is no longer in use. The problem arises when another process tries to access the same resource between steps 1 and 2.
This can lead to unpredictable results. Processes can be locked or hijacked to gain greater rights from another process, causing security problems. The attacks are concentrated on programs with larger permissions (called setuid programs). A race condition attack usually exploits resources that are accessible to the program while it is executing. Low-privileged programs also pose a security risk because an attacker may wait for a user with higher privileges to execute the program (such as root) before attacking.
The following tips can help mitigate race edition attacks:
When operating on files, use functions that use file descriptors instead of those that use file paths (such as using fdopen() instead of fopen()). File descriptors prevent malicious users from using file concatenations (symbolic or physical) to change files when they are opened or before the original process operates on them.
Use the FCNTL () and flock() functions to lock files when they are written or even read so that they cannot be accessed by other processes. It can almost set up atomic operations.
Be careful with temporary file manipulation, as it often leads to race conditions.
1.1.4 Verify a valid return value
It is important to verify valid return values. One example is that the older /bin/login implementation did not check for an error return value, causing it to return root access when it could not find the /etc/passwd file. This is reasonable if the file is corrupted, but if the file exists but is inaccessible, then this is a big problem.