Der ältere Ubuntu-Bug wurde - soweit ich es gelesen habe - durch ein Kernel-Update behoben (irgendwo in 3.13+) und verhinderte, daß der "vsftpd" überhaupt gestartet werden konnte. Wenn ich das hier richtig verstanden habe, tritt die "500 OOPS"-Message erst beim Login-Versuch für Benutzer auf, bei denen auf Dateien im OS zurückgegriffen werden soll.
Der Patch in #40 greift dann aber deutlich in die Logik des Ablaufs ein ... bei einem Pointer-Underrun (also einer Operation, wo ein wilder Zugriff in die Page VOR der eigentlichen Page (bzw. vor dem eigentlichen Buffer) erfolgt - diese "protection" klappt aber ohnehin nur für Buffer, die als Vielfaches der Seitengröße angefordert wurden, denn ansonsten wird als Pointer die Adresse "Ende - Größe" zurückgegeben:
Code:
/* Before we make the "before" page inaccessible, store the size in it.
* A little hack so that we don't need to explicitly be passed the size
* when freeing an existing secure buffer
*/
*((unsigned int*)p_mmap) = round_up;
p_no_access_page = p_mmap;
vsf_sysutil_memprotect(p_no_access_page, page_size, kVSFSysUtilMapProtNone);
p_mmap += page_size;
if (page_offset) <<< Hier ist "page_offset" nicht 0, wenn die angeforderte Größe kein Vielfaches der Page-Size ist.
{
p_mmap += (page_size - page_offset);
}
*p_ptr = p_mmap;
) wird der Zugriff mit diesem Patch trotzdem funktionieren (solange er keinen Schreibversuch beinhaltet), denn die Seite ist jetzt erreichbar.
Das schwächt dann die Sicherheit (und damit die Idee) dieses "secure buffer" deutlich - gerade solche "Spezialitäten" sind es ja, die den "
very
secure
FTP daemon" eigentlich ausmachen - denn ein normales Programm würde sich den Teufel darum scheren, die Pages rund um einen Buffer so zu manipulieren, daß Zugriffe dort unmittelbar zum Abbruch führen.
Notfalls kann man das aber sicherlich wirklich machen (so es denn hilft, denn immerhin wird ja trotzdem statt read/write (wie es von "map_anon_pages()" kommt) auf read/only gesetzt), denn auch Buffer in "nicht-Page-Größe" sind ja (prinzipbedingt) gegen solchen "Pointer-Underrun" nicht gefeit.
Ich weiß allerdings nicht, welche Puffergrößen im "vsftpd" normalerweise angefordert werden ... jedoch zeigt schon der Blick in die "filestr.c" weiter vorne, daß da eben der "secure buffer" auch nur in der Dateigröße angefordert wird. Damit greift der Schnipsel oben in diesem Beitrag mit einer Wahrscheinlichkeit von 1/4096 (denn die Page-Size müßte 4 KB betragen, iirc) ohnehin und ein Pointer-Underrun würde gar nicht festgestellt, solange er "im Rahmen" (einer Seite) bleibt und die Dateilänge nicht zufällig gerade ein Vielfaches von 4 KB ist.
Allerdings würde ich ohnehin nicht verstehen, warum es für ein "munmap()" einen Unterschied machen sollte, ob eine Page nun "read/only" oder "none" gemappt ist - zumindest in der Theorie (wenn man mal Kernel-Bugs beim syscall() außer Acht läßt) dürfte das keinen Unterschied ergeben. Wenn es bei einem Zugriff auf die Seite Probleme geben sollte (da wäre das Ergebnis dann aber eben ein SIGSEGV und kein "rc <> 0" vom "munmap()") durch irgendwelche nicht aktualisierten Cache-Pages, dann verstehe ich das noch - in die Richtung geht m.E. auch der Patch von den Chinesen.
Ich wäre ja schon begeistert, wenn tatsächlich klar ist, welcher "munmap()"-Call denn hier in die Hose geht - ich weiß allerdings nicht, ob ein "back trace" beim Abbruch (so man einen passenden Fehler provoziert, der nicht abgefangen wird, wie beim "500 OOPS") auch im "vsftpd" etwas bringen würde an Erkenntnis, wo nun dieses Problem eigentlich wirklich seine Wurzeln hat.
Ich bin ja nach wie vor der Ansicht, daß es nur zwei potentielle Stellen mit "munmap()" (in der Folge eines anderen Funktionsaufrufs) gibt ... bei der ersten (also dem ersten Patchversuch) hätte es gar keine "500 OOPS: munmap" mehr geben dürften und wenn die bei "str_fileread()" unter den Bedingungen des zweiten Patches jetzt trotzdem noch auftreten (ich dachte da eigentlich auch weniger an das Handling von 0-Byte-Files im Up- oder Download, sondern eher an diverse, im FRITZ!OS nicht vorhandene Dateien, wie "/etc/services" oder ähnliches), dann wäre wieder unerklärlich, warum es
irgendeinen Fall gibt, bei dem es dann nicht zum Fehler kommt, wenn man es nur oft genug wiederholt.
Als Notnagel bliebe nur noch die Vermutung, daß das Ermitteln der Page-Size hin und wieder nicht klappt ... denn dieser Wert wird natürlich bei den ganzen Kunststückchen zum Memory-Mapping auch jedesmal abgefragt und alle Berechnungen für die Pointer verlassen sich darauf, daß der jedesmal dasselbe Ergebnis liefert. Ermittelt wird diese Größe aber auch nur einmalig und anschließend wird sie in einer statischen Variablen gespeichert:
Code:
unsigned int
vsf_sysutil_getpagesize(void)
{
static unsigned int s_page_size;
if (s_page_size == 0)
{
s_page_size = getpagesize();
if (s_page_size == 0)
{
die("getpagesize");
}
}
return s_page_size;
}
Wenn jetzt aus irgendeinem kruden Grund an die Adresse von "s_page_size" geschrieben werden sollte (das muß hier ja auch beim Start noch auf 0 initialisiert werden, damit das klappt - aber das macht der Compiler eigentlich für statische Variablen automatisch so) und das ist am Ende nicht mehr die tatsächliche Seitengröße, dann käme die gesamte Pointerberechnung beim "munmap()" auch wieder durcheinander.
Das wäre dann ebenso ein Ansatz für weitere Tests, wie die zusätzliche Ausgabe des tatsächlichen Fehlercodes, der vom "munmap()" zurückgeliefert wird und der beim Aufruf verwendeten Parameter - im Moment ist ja nur klar, daß das Ergebnis nicht die erwartete "0" ist ... hinsichtlich des Fehlers herrscht aber weitgehende Unklarheit.
Ich bin mal gespannt, wie sich das hier entwickelt ... etwas komisch ist das schon. Da kommt dann noch die neue uClibc-ng hinzu, die ja auch aus dem "getpagesize()" erst mal den passenden Syscall machen müßte oder gleich selbst beim Build der Library wieder das Symbol PAGE_SIZE des Kernels als statische Variable speichern könnte. Mal schauen, was uClibc-ng da tatsächlich macht und ob/wie sich das von der älteren uClibc unterscheiden mag, die vor 07.0x verwendet wurde.