Was ist das Ergebnis von (unsigned int) * (int)
im Fall von Integer-Überläufen? unsigned
oder int
? Welchen Typ akzeptiert der Array-Indexoperator ( operator[]
) für char*
: int
, unsigned int
oder etwas anderes?
Ich auditierte die folgende Funktion und plötzlich stellte sich diese Frage. Die Funktion hat eine Sicherheitslücke in Zeile 17.
%Vor% Beachten Sie, dass sowohl w
als auch h
sehr große Ganzzahlen ohne Vorzeichen sind. Die Multiplikation in Zeile 9 hat eine Chance, die Validierung zu bestehen.
Jetzt ist das Problem in Zeile 17. Multiply int i
mit unsigned int w
: Wenn das Ergebnis int
ist, ist es möglich, dass das Produkt negativ ist, was zu einer Position führt, die vor buf
liegt. Wenn das Ergebnis unsigned int
ist, ist das Produkt immer positiv, was den Zugriff auf eine Position nach buf
ermöglicht.
Es ist schwer, Code zu schreiben, um dies zu rechtfertigen: int
ist zu groß. Hat jemand Ideen dazu?
Gibt es eine Dokumentation, die den Typ des Produkts angibt? Ich habe danach gesucht, aber bis jetzt noch nichts gefunden.
Ich nehme an, dass, was die Sicherheitslücke betrifft, ob (unsigned int) * (int)
unsigned int
oder int
erzeugt, spielt keine Rolle, weil sie in der kompilierten Objektdatei nur Bytes sind. Der folgende Code funktioniert unabhängig vom Typ des Produkts gleich:
Daher ist es egal, welchen Typ die Multiplikation zurückgibt. Es kommt darauf an, ob die Verbraucherfunktion int
oder unsigned
benötigt.
Die Frage hier ist nicht wie schlecht die Funktion ist oder wie man die Funktion verbessert, um sie zu verbessern. Die Funktion hat zweifellos eine Schwachstelle. Die Frage ist das genaue Verhalten der Funktion, basierend auf dem vorgeschriebenen Verhalten der Standards.
Um Ihre Frage zu beantworten: Der Typ eines Ausdrucks, der ein int und ein unsigned int multipliziert, ist ein unsigned int in C / C ++.
Um Ihre implizite Frage zu beantworten, gibt es einen vernünftigen Weg, mit einem möglichen Überlauf bei der Ganzzahlarithmetik umzugehen, indem Sie den " IntSafe
" Satz von Routinen von Microsoft verwenden:
Sie ist im SDK verfügbar und enthält Inline-Implementierungen, sodass Sie untersuchen können, was sie tun, wenn Sie sich auf einer anderen Plattform befinden.
Der Typ von w*i
ist in Ihrem Fall nicht signiert. Wenn ich den Standard richtig lese, lautet die Regel, dass die Operanden in den größeren Typ (mit seiner Signedness) konvertiert werden, oder vorzeichenloser Typ, der dem signierten Typ entspricht (in Ihrem Fall unsigned int
).
Aber selbst wenn es nicht vorzeichenbehaftet ist, verhindert es nicht die Umgehung (Schreiben in den Speicher vor buf
), da dies der Fall sein kann (auf der i386-Plattform), dass p[-1]
dasselbe ist %Code%. Wie auch immer, in Ihrem Fall wären sowohl p[-1u]
als auch buf[-1]
ein undefiniertes Verhalten, daher ist die Frage mit der Signatur / unsigned nicht so wichtig.
Beachten Sie, dass in anderen Kontexten signierte / unsignierte Angelegenheiten - z. buf[big unsigned number]
gibt unterschiedliche Ergebnisse, abhängig von den Typen von (int)(x*y/2)
und x
, auch wenn kein undefiniertes Verhalten vorliegt.
Ich würde Ihr Problem lösen, indem Sie in Zeile 9 nach einem Überlauf suchen. da 4096 eine ziemlich kleine Konstante ist und 4096 * 4096 auf den meisten Architekturen nicht überläuft (das muss man überprüfen), würde ich
machen %Vor% Dies lässt den Fall aus, wenn y
oder w
0 sind, sollten Sie bei Bedarf nachsehen.
Im Allgemeinen könnten Sie wie folgt nach einem Überlauf suchen:
%Vor% In C / C ++ ist die Schreibweise p[n]
wirklich eine Abkürzung, um *(p+n)
zu schreiben, und diese Zeigerarithmetik berücksichtigt das Vorzeichen. So ist p[-1]
gültig und bezieht sich auf den Wert unmittelbar vor *p
.
Also ist das Zeichen hier wirklich wichtig, das Ergebnis des arithmetischen Operators mit Integer folgt einem Satz von Regeln, die vom Standard definiert werden, und dies wird Integer-Promotions genannt.
Sehen Sie sich diese Seite an: INT02-C. Verstehen Sie ganzzahlige Konvertierungsregeln
2 Änderungen machen es sicherer:
%Vor%Beachten Sie auch, dass es keine schlechte Idee ist, in das Pufferende zu schreiben oder von ihm zu lesen. Die Frage ist also nicht, ob i w negativ werden kann, sondern ob 0 & lt; = i h + w & lt; = 4096 gilt.
Es kommt also nicht auf den Typ an, sondern auf das Ergebnis von h * i. Zum Beispiel macht es keinen Unterschied, ob dies (vorzeichenlos) 0x80000000 oder (int) 0x80000000 ist, das Programm wird trotzdem seg-fault.
Für C, siehe "Übliche arithmetische Umwandlungen" (C99: Abschnitt 6.3.1.8, ANSI C K und R A6.5) für Details, wie die Operanden der mathematischen Operatoren behandelt werden.
In Ihrem Beispiel gelten die folgenden Regeln:
C99:
Ansonsten, wenn der Typ des Operanden mit vorzeichenbehafteten Integer-Typ kann darstellen alle Werte des Typs der Operand mit vorzeichenloser Ganzzahl, dann der Operand mit vorzeichenloser Ganzzahl Der Typ wird in den Typ des Typs konvertiert Operand mit Ganzzahl mit Vorzeichen.
Ansonsten werden beide Operanden konvertiert zum vorzeichenlosen Integer-Typ entsprechend dem Typ der Operand mit Ganzzahl mit Vorzeichen.
ANSI C:
Andernfalls wird, wenn einer der beiden Operanden unsigned int ist, der andere in unsigned int konvertiert.
Warum deklariere ich nicht einfach i als unsigned int? Dann verschwindet das Problem.
In jedem Fall ist i * w garantiert & lt; = 4096, da der Code dies testet, so dass es niemals überläuft.
memcpy (& amp; buf [i w & gt; -1? i w & lt; 4097? i w: 0: 0], init, w); Ich glaube nicht, dass die dreifache Berechnung von i w die Leistung verschlechtert
w * h könnte überlaufen, wenn w und / oder h ausreichend groß sind und die folgende Validierung bestehen könnte.
%Vor%Bei int, unsigned int gemischten Operationen wird int auf unsigned int gesetzt, wobei in diesem Fall ein negativer Wert von 'i' zu einem großen positiven Wert würde. In diesem Fall
%Vor%würde auf einen nicht gebundenen Wert zugreifen.
Vorzeichenlose Arithmetik wird als modular (oder wrap-around) ausgeführt, so dass das Produkt von zwei großen vorzeichenlosen Ints leicht kleiner als 4096 sein kann. Die Multiplikation von int und unsigned int führt zu einem vorzeichenlosen int (siehe Abschnitt 4.5 der C ++ - Standard).
Daher können Sie bei gegebenem großem w und einem geeigneten Wert von h tatsächlich in Schwierigkeiten geraten.
Es ist schwierig sicherzustellen, dass Ganzzahlarithmetik nicht überläuft. Ein einfacher Weg besteht darin, zu Gleitkomma zu konvertieren und eine Gleitkomma-Multiplikation durchzuführen und zu sehen, ob das Ergebnis überhaupt in Ordnung ist. Wie von QWERTY vorgeschlagen, wäre long long verwendbar, wenn es auf Ihrer Implementierung verfügbar wäre. (Es ist eine gemeinsame Erweiterung in C90 und C ++, existiert in C99 und wird in C ++ 0x sein.)
Es gibt 3 Absätze im aktuellen C1X-Entwurf zur Berechnung (UNSIGNED TYPE1) X (SIGNED TYPE2) in 6.3.1.8 Übliche arithmetische Umsetzungen, N1494,
AG 14: C - Projektstatus und Meilensteine
Andernfalls, wenn der Operand, der einen Integer-Typ ohne Vorzeichen hat, den Rang größer oder hat gleich dem Rang des Typs des anderen Operanden, dann der Operand mit Der vorzeichenbehaftete ganzzahlige Typ wird in den Typ des Operanden konvertiert, der nicht vorzeichenbehaftet ist Integer-Typ.
Andernfalls, wenn der Typ des Operanden mit vorzeichenbehafteten Integer-Typ darstellen kann alle Werte des Typs des Operanden mit vorzeichenlosen Integer-Typ, dann Der Operand mit vorzeichenloser Ganzzahl wird in den Typ der. konvertiert Operand mit Ganzzahl mit Vorzeichen.
Andernfalls werden beide Operanden in den vorzeichenlosen Integer-Typ konvertiert entspricht dem Typ des Operanden mit Vorzeichen vom Typ Integer.
Wenn also a nicht vorzeichenbehaftet ist und b int ist, sollte das Parsen von (a * b) Code erzeugen (a * (unsigned int) b). Wird überlaufen, wenn b & lt; 0 oder a * b & gt; UINT_MAX.
Wenn a unsigned int ist und b eine längere Größe hat, sollte (a * b) ((long) a * (long) b) erzeugen. Wird überlaufen, wenn a * b & gt; LONG_MAX oder a * b & lt; LONG_MIN.
Wenn a nicht vorzeichenbehaftet int ist und b lang ist und dieselbe Größe hat, sollte (a * b) erzeugen ((vorzeichenlos lang) a * (vorzeichenlos lang) b). Wird überlaufen, wenn b & lt; 0 oder a * b & gt; ULONG_MAX.
Bei Ihrer zweiten Frage zu dem Typ, der von "indexer" erwartet wird, erscheint die Antwort "integer type", die jeden (vorzeichenbehafteten) ganzzahligen Index zulässt.
6.5.2.1 Array-Subskribierung
Einschränkungen
1 Einer der Ausdrücke muss den Typ "Zeiger auf den vollständigen Objekttyp" haben, der andere Der Ausdruck muss einen Integer-Typ haben, und das Ergebnis hat den Typ '' type ''.
Semantik
2 Ein postfix-Ausdruck gefolgt von einem Ausdruck in eckigen Klammern [] ist ein subskribierter Ausdruck Bezeichnung eines Elements eines Array-Objekts. Die Definition des tiefgestellten Operators [] ist, dass E1 [E2] identisch zu (* ((E1) + (E2))) ist. Wegen der Konvertierungsregeln das Wenden Sie sich an den binären Operator +, wenn E1 ein Array-Objekt ist (äquivalent, ein Zeiger auf die Anfangselement eines Array-Objekts) und E2 ist eine ganze Zahl, E1 [E2] bezeichnet die E2-te Element von E1 (von Null ausgehend).
Es liegt am Compiler, eine statische Analyse durchzuführen und den Entwickler vor der Möglichkeit eines Pufferüberlaufs zu warnen, wenn der Zeigerausdruck eine Arrayvariable ist und der Index negativ sein kann. Gleiches gilt für die Warnung über mögliche Überschreitungen der Array-Größe, selbst wenn der Index positiv oder unsigniert ist.
Um Ihre Frage tatsächlich zu beantworten, ohne die Hardware anzugeben, auf der Sie laufen, wissen Sie nicht, und in Code, der portabel sein soll, sollten Sie sich nicht auf ein bestimmtes Verhalten verlassen.