[OSy] pointer arithmetics overflow

Adam Hraska smudloadam at gmail.com
Wed Feb 23 00:56:14 CET 2011


Dobry den,

zaujimalo by ma, ako zapisat v standardnom C pripadne
C++ test, ktory overi, ze volna oblast zacinajuca
na "freeStart" a dlha "freeSize" byte-ov nezasahuje
do naalokovanej oblasti zacinajucej na "nextStart".
Vieme pritom, ze freeStart < nextStart. freeStart
i freeSize urcuje uzivatel.

Tato uloha sa vyskytla pri implementacii fcii vma_map
s VF_VA_USER. Problemom je napisat test bez pointer
arithmetic overflow.

Diagram situacie:

+----------------+ ... +------
| <- freeSize -> |     ` nextStart
` freeStart


1) Prvy pokus:

bool isOk(char *freeStart, size_t freeSize, char *nextStart)
{
	ASSERT(freeStart < nextStart);
	if(freeStart + freeSize <= nextStart){
		// Free sa zmesti pred oblast nextStart.
		return true;
	}
	else{
		// Free oblast koliduje s nextStart.
		return false;
	}
}

Tento fragment ma problem, ze freeStart + freeSize
moze overflow-nut pointer, co je podla standardu
undefined behavior [1] a v praxi fatalna chyba.


2) Pokus s overflow testom:

if(freeStart + freeSize < freeStart){
	return (freeStart + freeSize <= nextStart);
}
else{
	// Koliduje!
	return false;
}

V tomto fragmente sa najprv pokusame overit, ci nedojde
k pointer overflow. Bohuzial, pri teste na pointer
overflow sposobujeme samotny overflow. Standard jasne
specifikuje, ze pointer overflow je undefined behavior
a v praxi gcc uvedeny test kompletne odstrani ako sucast
optimalizacie.


3) Rozdiel pointerov.

if(freeSize <= nextStart - freeStart){
	// Free sa zmesti pred nextStart.
	return true;
}
else{
	// Koliduje!
	return false;
}

K ziadnemu pointer overflowu tentokrat nedochadza.
Bohuzial, rozdiel pointerov je podla standardu typu
ptrdiff_t, co je signed integer typ [2]. Podla [3] je
overflow signed integer-u implementation-defined.
Nemozeme sa teda spolahnut, ze prekladac vygeneruje
vhodny kod pre situaciu: INT_MAX < nextStart - freeStart.


4) Pseudo-riesenie.
V nasom kerneli sme sa nakoniec rozhodli pre variantu
(3), kedze v praxi prekladac pocita i signed arithmetics
v two's complement, ktory sa vhodne konvertuje na unsigned
size_t pri porovnani s freeSize. Dalej sme sa poistili
flagmi -fwrapv a -fno-strict-overflow [4].

Zaver
-----
Zaujimalo by ma, ako sa dari realnym OS vyriesit tuto
elementarnu operaciu bez vyuzivania nestandardneho
chovania prekladaca (napr. flagov).

Dakujem.

S pozdravom

Adam Hraska

PS: Uvedomujem si, ze na MIPS je najvacsi segment
velky INT_MAX + 1, a tak by stacilo najprv testovat,
ci sa vobec volna oblast cela zmesti do segmentu.
Tento pristup vsak nemozno aplikovat na architekturach,
kde mozu mat segmenty vyrazne vacsiu velkost (napr.
x86).

[1] c++98.std.expr.add 5.7.5
[2] c++98.std.expr.add 5.7.6
[3] c++98.std.conv.intergral 4.7.3
[4] http://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html




More information about the NSWI004 mailing list