Neither variant is safe if an attacker controls your inputs
The expression p1 + len < p2
compiles down to something like p1 + sizeof(*p1)*len < p2
, and the scaling with the size of the pointed-to type can overflow your pointer:
int *p1 = (int*)0xc0ffeec0ffee0000;
int *p2 = (int*)0xc0ffeec0ffee0400;
int len = 0x4000000000000000;
if(p1 + len < p2) {
printf("pwnd!\n");
}
When len
is multiplied by the size of int
, it overflows to 0
so the condition is evaluated as if(p1 + 0 < p2)
. This is obviously true, and the following code is executed with a much too high length value.
Ok, so what about p2-p1 < len
. Same thing, overflow kills you:
char *p1 = (char*)0xa123456789012345;
char *p2 = (char*)0x0123456789012345;
int len = 1;
if(p2-p1 < len) {
printf("pwnd!\n");
}
In this case, the difference between the pointer is evaluated as p2-p1 = 0xa000000000000000
, which is interpreted as a negative signed value. As such, it compares smaller then len
, and the following code is executed with a much too low len
value (or much too large pointer difference).
The only approach that I know is safe in the presence of attacker-controlled values, is to use unsigned arithmetic:
if(p1 < p2 &&
((uintptr_t)p2 - (uintptr_t)p1)/sizeof(*p1) < (uintptr_t)len
) {
printf("safe\n");
}
The p1 < p2
guarantees that p2 - p1
cannot yield a genuinely negative value. The second clause performs the actions of p2 - p1 < len
while forcing use of unsigned arithmetic in a non-UB way. I.e. (uintptr_t)p2 - (uintptr_t)p1
gives exactly the count of bytes between the bigger p2
and the smaller p1
, no matter the values involved.
Of course, you don't want to see such comparisons in your code unless you know that you need to defend against determined attackers. Unfortunately, it's the only way to be safe, and if you rely on either form given in the question, you open yourself up to attacks.