Folgender Code wurde in einem Programmteil von uns eingebaut:
template <class T>
void SecureClearString(T &strText)
{
::SecureZeroMemory(strText.GetBuffer(0),strText.GetAllocLength());
strText.Empty();
}
Der Sinn und Zweck sollte sein, dass der Inhalt einer CString Variable durch diesen Code überschrieben und anschließend freigegeben wird, damit zum Beispiel ein Kennwort oder ein Benutzername nicht mehr im Speicher lesbar bleibt.
Die Anwendung sieht in etwa so aus (war allerdings noch in einer Klasse gekapselt):
CString strPassword;
...
// Fill password and use it
...
SecureClearString(strPassword);
Doch leider ist was faul mit dem Code… zwei Probleme gibt es mit diesem Stück Code.
Meine Frage an meine Leser lautet nun was ❓
„If you use the pointer returned by GetBuffer to change the string contents, you must call ReleaseBuffer before using any other CString member functions.“
Nein! Das sind nicht die zwei Fehler…
Das mag in der Doku stehen, ist aber nicht notwendig.
Schon gar nicht wenn Empty direkt danach aufegrufen wird.
ReleaseBuffer ist auch nicht nötig wenn sich die interne String-Länge nicht ändert. Ich habe diesen Kommentar noch nie verstanden, denn keine der bisherigen Implementierungen hat wirklich etwas Entscheidendes gemacht, außer evtl. die Stringlänge eben neu zu setzen.
GetAllocLength() gibt zurück „The number of characters allocated for this object.“.
Aber SecureZeroMemory arbeitet mit Bytes, nicht Zeichen.
Also wenn Unicode verwendet wird, wird nur die hälfte des Strings gelöscht.
@Stefan: Bingo! Fehler Nummer 1 gefunden…
Aber Fehler Nummer 2? Der kann sogar zu einem System-Crash führen… 😉
GetBuffer darf nicht mit dem Argument 0 aufgerufen werden, denn dieser Wert ist „The minimum size of the character buffer in characters. This value does not include space for a null terminator“. Diese Größe ist wohl die Länge dessen, was man maximal überschreiben darf. 0 klingt hier nicht so sicher 😉
Und?
Nun ja, CStrings verwenden „reference counting and copy & write“. Das bedeutet, dass zwei oder CString Variablen den gleichen Puffer verwenden können. Ich vermute einmal, dass bei Verwendung von GetBuffer dieses ggf. vorhandene „Aliasing“ als erstes aufgelöst wird. Sprich es wird eine Kopie des Puffers gemacht. Dabei wird bei GetBuffer vermutlich nur so viel Puffer allokiert, wie als Argument von GetBuffer übergeben wird. In diesem Fall also nichts oder fast nichts. Wenn man aber Daten in einen nicht allokierten Speicherbereich kopiert, können lustige Sachen passieren. Die Frage ist, was passiert, wenn der String Puffer vorher einen Reference Count von 1 hat (kein Aliasing). Dann geht es vermutlich gut.
Mal sehen, ob ich komplett daneben liege. Bestimmt kann Martin mehr Licht ins Dunkle bringen…
@Stefan: Du bist auf einem guten Weg, das Problem zu lösen. Aber Deine Schlussfolgerungen sind nicht ganz richtig.
GetBuffer(0) ist ein absolte korrekter Weg einen gültigen schreibbaren Zeiger auf einen CString zu bekommen. Wichtig ist, dass sich in diesem Fall die Länge nicht ändern darf.
Aber Dein Gedanke mit dem Copy-on-Write ist absolut die richtige Richtung.
Spannendes Rätsel.
Aber ich komm auch nicht drauf. 🙂
Stefan hat recht GetBuffer(0) ist blöd. GetBuffer() wäre sauberer.
Der Funktion ist es aber egal, sie will nur die Länge wissen, die man verändern will um gegebenfalls den Refcount aufzulösen und intern ein neues String Objekt zu erstellen. Etwas doofe Doku.
Nun, falls ihr also 2 Objekte auf diesen String habt löst ihr mit GetBuffer auf jedenfall den RefCount auf, und eure Funktion ändert nur ein Objekt.
Das Andere behält den ursprünglichen String.
Das ist vielleicht nicht gewünscht ?
Aber warum soll das Ganze ein Systemcrash verursachen ?
Ist doch alles im Userspace und sogar inline. grübel. 🙂
Und warum ein template ?
Benutzt ihr auch ein Derivat von CString ?
Da werdet ihr aber wohl nicht etwas unglücklich überschrieben haben.
Ne, das wird es wohl nicht sein. 😉