Make your own safe C functions
Introduction
blah blah blah sorry I'm too lazy to write this part. The point is if the safe function alternative is not available in your system (as pointed out in the mid section of this article), you can always make your own version of safe function.
What is memcpy?
memcpy is a standard C function that copies data from one location to another. It is declared in the string.h header file and is widely used in C programming.
void* memcpy(void* dest, const void* src, size_t n);
The memcpy function takes three arguments: dest, src, and n. dest is the destination address where the data will be copied, src is the source address where the data is located, and n is the number of bytes to be copied.
Overlapping memory regions
The main issue here memcpy does not check overlapping regions and may produce incorrect results (the dreaded undefined behavior)
Take a look at this simple C program:
int main()
{
char buffer[10] = "abcdefgh";
memcpy(buffer+3, buffer, 5);
printf("\nUsing memcpy: \n");
for (int i=0; i<10; i++) {
printf("%c ", buffer[i]);
}
printf("\n");
return 0;
}
Compiling the program it will result in:
a b c a b c d b
which is incorrect. You might encounter other variation of results, different from what's shown above.
Create a safe alternative: s_memcpy
Check out this syntax for s_memcpy (a custom function):
void* s_memcpy(void* dest, const void* src, size_t n)
{
assert(dest != NULL);
assert(src != NULL);
assert(n > 0);
if (dest == src) {
return dest; // no need to copy
}
if ((uintptr_t)dest < (uintptr_t)src + n && (uintptr_t)dest + n > (uintptr_t)src) {
// Memory regions overlap, use memmove
return memmove(dest, src, n);
}
// Memory regions do not overlap, use memcpy
return memcpy(dest, src, n);
}
Detailed explanation:
- The s_memcpy uses assert to check for null pointers and ensure non-zero size. This prevents issues before copying.
- Also by casting pointers to uintptr_t the function checks if the memory regions overlap. This is crucial for deciding whether to use memcpy or memmove.
- If it detects overlap, it switches to using memmove, which is designed to handle overlaps safely. memmove copies the data in a way that avoids overwriting it before it's copied.
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>
#include <stdint.h>
int main()
{
char buffer2[10] = "abcdefgh";
s_memcpy(buffer2+3, buffer2, 5);
printf("\nUsing s_memcpy: \n");
for (int i=0; i<10; i++) {
printf("%c ", buffer2[i]);
}
printf("\n");
return 0;
}
Program's result:
a b c a b c d e
This is the correct output.
Use compiler flags!
The easiest method is to use compiler flags. Modern C compilers are powerful you'd be stupid if you don't leverage its features to help you write safe C programs!
I use alias when I compile C program:
alias gcc-14_safest='gcc-14 -ggdb -Wall -Werror -Wextra -fsanitize=address -fno-omit-frame-pointer'
Try compile the first memcpy example code using the above compiler flags, it would throw error right away and you would know on the spot something's incorrect and need fixing.
error: 'memcpy' accessing 5 bytes at offsets 3 and 0 overlaps 2 bytes at offset 3 [-Werror=restrict]
Summary
- Use compiler flags --> they are extremely powerful!
- Look up for safer functions if available.
- Create your own if it's not available on your system!