Forum: PC-Programmierung Linux C "free(): double free detected in tcache2


von Joachim D. (Firma: JDCC) (scheppertreiber)


Lesenswert?

Servus,

es klemmt mal wieder.

Ich bekomme die Fehlermeldung nach fork() und Ausführen des Threads
unmittelbar bei oder vor exit( 0);. Alle free() in allen Dateien sind
durch eine Abfrage ob der pointer != NULL ist abgesichert.

Im Web gibt es einige Seiten die auf ein Problem mit dem Thread-Cache
hinweisen - so ganz blicke ich da aber nicht durch (vor Allem was ich
da machen kann).

Hat vielleicht jemand eine Idee ?

Ubuntu, GCC

von MaWin (Gast)


Lesenswert?

Joachim D. schrieb:
> Alle free() in allen Dateien sind
> durch eine Abfrage ob der pointer != NULL ist abgesichert.

unnötig.

> free(): double free detected

Das bedeutet, dass du free() zwei mal für den selben Pointer ausrufst.
Das kann auch jeweils einmal aus zwei Threads passieren.

Das ist ein schwerer Fehler und führt zu Undefined Behavior.

von Oliver S. (oliverso)


Lesenswert?

Joachim D. schrieb:
> Alle free() in allen Dateien sind
> durch eine Abfrage ob der pointer != NULL ist abgesichert.

Gegen was genau?

Ansonsten wird das Problem genau das sein, was die Fehlermeldung besagt.

Oliver

von foobar (Gast)


Lesenswert?

fork erzeugt einen neuen Prozess, keinen Thread - gemeinsam genutzter 
Speicher entfällt also.  exit in Kindprozessen ist meist unpassend - 
üblich ist _exit.  Der überflüssige NULL-check vor free ist bereits 
genannt worden.  free wird auch innerhalb der Libraries genutzt - ein 
doppelter fclose z.B. erzeugt auch so eine Meldung.

von Irgend W. (Firma: egal) (irgendwer)


Lesenswert?

Joachim D. schrieb:
> Alle free() in allen Dateien sind
> durch eine Abfrage ob der pointer != NULL ist abgesichert
Das funktioniert aber nur wenn du den Pointer auch irgendwann auf Null 
setzt. Dein free macht das nicht von alleine.

von Joachim D. (Firma: JDCC) (scheppertreiber)


Lesenswert?

Die free()'s habe ich pingelig kontrolliert, da ist eigentlich
nichts. Ein free() auf einen schon freigebenen ptr läßt den
Prozess eigentlich nicht crashen.

Was bedeutet das tcache2 ?

Die Meldung kommt unmittelbar am Programmende, vor oder mit
exit() oder _exit().

von Joachim D. (Firma: JDCC) (scheppertreiber)


Lesenswert?

Am Prozessende ;)

von Norbert (Gast)


Lesenswert?

Ich kann deinen Sourcecode von hier aus nicht erkennen,
aber mein erster Gedanke:

malloc() vor dem fork()?
free() in den beiden Prozessen?

Dat geit net.

fork() klont dir zwar die Pointer, aber nicht den allozierten Speicher.

von MaWin (Gast)


Lesenswert?

Joachim D. schrieb:
> Ein free() auf einen schon freigebenen ptr läßt den
> Prozess eigentlich nicht crashen.

Eben doch. Das ist ein Double-Free. Ein schwerer Speicherfehler. Das ist 
Undefined Behavior.

> Die free()'s habe ich pingelig kontrolliert, da ist eigentlich
> nichts.

Dann kontrolliere es noch einmal.

Oder entferne einfach mal alle free()-Aufrufe und lecke den Speicher.
Tritt es immer noch auf? Nein -> Dann wirds wohl einer deiner 
free()-Aufrufe gewesen sein. Ja -> Dann wirds wohl irgendeine andere 
Ressource über Bande sein, die du doppelt freigibst.

von MaWin (Gast)


Lesenswert?

Norbert schrieb:
> fork() klont dir zwar die Pointer, aber nicht den allozierten Speicher.

Ehm, doch. Natürlich tut es das. (Genauer gesagt legt es eine COW-Kopie 
an).

von Joachim D. (Firma: JDCC) (scheppertreiber)


Lesenswert?

1
        while( 1){
2
                size = sizeof( a1);
3
                fd = accept( s, (struct sockaddr*) &a1, &size);
4
5
                if ( ( pid = fork()) < 0) {
6
                        printf( "\nfork err %s", strerror( errno));
7
                } else {
8
                        if ( pid == 0) {
9
                                close( s);
10
                                DoCmd( fd);
11
                        } else {
12
                                close( fd);
13
                        }
14
                }
15
        }
1
       static  int
2
        DoCmd(
3
        int fd
4
        )
5
{
6
        int     i, st, size, pid;
7
        char    http_header[4096];
8
        char    *p, *q, ip[256], req[1044], str[2048];
9
        char    *tmp = NULL;
10
11
        JA( "****************************** INCOMING DATA %s / %s ************************************", sysdatel(), systime());
12
13
        jStartTime();
14
        LoadServicesConf();
15
16
        strcpy( ip, inet_ntoa( a1.sin_addr));
17
18
        // vorher mal Speicher resrvieren, bei GET wird die Länge nicht
19
        // übergeben. 16kB reichen für GET.
20
21
        if ( (tmp = calloc( 1, 16384)) == NULL)
22
                ExitError( "GetRecData", "A alloc err tmp");
23
24
        size = GetRecData( fd, &tmp, ip);
25
        memset( http_header, 0, sizeof( http_header));
26
        mopen( tmp, strlen( tmp));
27
        st = 0;
28
        while ( mgetstrim( str)){
29
                if ( ! *str)
30
                        break;
31
                appsprintf( http_header, "%s\n", str);
32
                st += strlen( str);
33
        }
34
35
        printf( "\n  %s %-16s %8d Byte ", systime(), ip, size);
36
        fflush( stdout);
37
38
        // Kram ist da, also ausführen
39
40
        if ( (sfp = fopen( tmpfilename( ip), "w")) == NULL){
41
                JA( "open error %s %s", tmpfilename( ip), strerror( errno));
42
                exit( 0);
43
        }
44
45
        GetInputData( fd, tmp, size, ip, http_header);
46
47
        fclose( sfp);
48
49
        if ( tmp != NULL)
50
                free( tmp);
51
52
        // Senden des Ergebnisses
53
54
//        SendFileToHTTP( fd, ip);
55
        close( fd);
56
        _exit( 0);
57
}
(Auszug) tmp bekommt in GetRecData() evtl. per realloc()
mehr Platz wenn's ein POST ist.

von Joachim D. (Firma: JDCC) (scheppertreiber)


Lesenswert?

MaWin schrieb:
> Oder entferne einfach mal alle free()-Aufrufe und lecke den Speicher.
> Tritt es immer noch auf? Nein -> Dann wirds wohl einer deiner
> free()-Aufrufe gewesen sein. Ja -> Dann wirds wohl irgendeine andere
> Ressource über Bande sein, die du doppelt freigibst.

Habe ich schon probiert (wenn der Prozess endelt sollte er ja
eh alles freigeben). Vielleicht ein überzähliges close() ?
Was ist dieser tcache 2 ?

von Joachim D. (Firma: JDCC) (scheppertreiber)


Lesenswert?

Auf stackoverflow gefunden:



The Thread Local Cache (tcache) is a performance optimization in glibc. 
Unfortunately, it comes at the expense of security and make some attacks 
much easier, as you have since discovered.

From 
https://sourceware.org/glibc/wiki/MallocInternals#Thread_Local_Cache_.28tcache.29

    While this malloc is aware of multiple threads, that's pretty much 
the extent of its awareness - it knows there are multiple threads. There 
is no code in this malloc to optimize it for NUMA architectures, 
coordinate thread locality, sort threads by core, etc. It is assumed 
that the kernel will handle those issues sufficiently well.

    Each thread has a thread-local variable that remembers which arena 
it last used. If that arena is in use when a thread needs to use it the 
thread will block to wait for the arena to become free. If the thread 
has never used an arena before then it may try to reuse an unused one, 
create a new one, or pick the next one on the global list.

    Each thread has a per-thread cache (called the tcache) containing a 
small collection of chunks which can be accessed without needing to lock 
an arena. These chunks are stored as an array of singly-linked lists, 
like fastbins, but with links pointing to the payload (user area) not 
the chunk header. Each bin contains one size chunk, so the array is 
indexed (indirectly) by chunk size. Unlike fastbins, the tcache is 
limited in how many chunks are allowed in each bin (tcache_count). If 
the tcache bin is empty for a given requested size, the next larger 
sized chunk is not used (could cause internal fragmentation), instead 
the fallback is to use the normal malloc routines i.e. locking the 
thread's arena and working from there.

von MaWin (Gast)


Lesenswert?

Ich würde es mal mit Valgrind laufen lassen.
Der sagt dir höchstwahrscheinlich genau mit Backtrace, wo dein Fehler 
ist.

von Joachim D. (Firma: JDCC) (scheppertreiber)


Lesenswert?

valgrind ? Was ist denn das ? (bin noch ziemlich neu in Linux)

von MaWin (Gast)


Lesenswert?

Ein sehr mächtiger Speicherfehler-Checker.
Wird bei praktisch allen Distributionen als Paket mitgeliefert.

von Oliver S. (oliverso)


Lesenswert?

Joachim D. schrieb:
> Auf stackoverflow gefunden:

Das sind glibc-Internas, die nichts mit deinem Problem zu tun haben
Die ändern überhaupt nichts daran, daß dein Code Speicher mehrfach 
freigibt. Das fällt halt dort auf.

Was du auch mal überprüfen könntest, ist, ob die Arrays überlaufen. Das 
könnte tmp überschreiben.

Oliver

von Joachim D. (Firma: JDCC) (scheppertreiber)


Lesenswert?

Also Folgefehler ... kann sein.

Ich schau nochmal alles durch.

valgrind  schau ich mir mal an.

Vielen Dank schon mal.

von Georg A. (georga)


Lesenswert?

Joachim D. schrieb:
> valgrind  schau ich mir mal an.

https://valgrind.org/docs/manual/quick-start.html#quick-start.mcrun

Einfach nur ein paar Zeichen vor den Programmaufruf. Und dann hat man 
oft länger was an den Meckereien zu knabbern ;)

von Joachim D. (Firma: JDCC) (scheppertreiber)


Lesenswert?

Georg A. schrieb:
> https://valgrind.org/docs/manual/quick-start.html#quick-start.mcrun

Das muß ich nur irgendwie über http zum Laufen kriegen oder den
Kram von der cmdline aufrufen ... Wir schon gehen ;)

von MaWin (Gast)


Lesenswert?

Joachim D. schrieb:
> Das muß ich nur irgendwie über http zum Laufen kriegen

Was hat das mit HTTP zu tun?
Du musst lediglich statt `meinprogramm` nun `valgrind meinprogramm` 
starten.

von Joachim D. (Firma: JDCC) (scheppertreiber)


Lesenswert?

Ist ein Webserver. Keine Kommandozeile. Habe eine zum Test eingebaut.

==6731== Memcheck, a memory error detector
==6731== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==6731== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright 
info
==6731== Command: ../../bin/uquery.x --test
==6731==
==6736== Invalid read of size 4
==6736==    at 0xA3BECFB: fclose@@GLIBC_2.2.5 (iofclose.c:48)
==6736==    by 0x10F766: DoCmd (httpsrv.c:177)
==6736==    by 0x10F46F: HttpServer (httpsrv.c:115)
==6736==    by 0x12CD26: main (uquery.c:124)
==6736==  Address 0xa58cbb0 is 0 bytes inside a block of size 472 free'd
==6736==    at 0xA32127F: free (in 
/usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==6736==    by 0xA3BEDA6: _IO_deallocate_file (libioP.h:862)
==6736==    by 0xA3BEDA6: fclose@@GLIBC_2.2.5 (iofclose.c:74)
==6736==    by 0x112F73: GetInputData (input.c:201)
==6736==    by 0x10F757: DoCmd (httpsrv.c:175)
==6736==    by 0x10F46F: HttpServer (httpsrv.c:115)
==6736==    by 0x12CD26: main (uquery.c:124)
...
==6731== LEAK SUMMARY:
==6731==    definitely lost: 0 bytes in 0 blocks
==6731==    indirectly lost: 0 bytes in 0 blocks
==6731==      possibly lost: 0 bytes in 0 blocks
==6731==    still reachable: 2,525 bytes in 7 blocks
==6731==         suppressed: 0 bytes in 0 blocks
==6731== Reachable blocks (those to which a pointer was found) are not 
shown.
==6731== To see them, rerun with: --leak-check=full 
--show-leak-kinds=all
==6731==
==6731== For lists of detected and suppressed errors, rerun with: -s
==6731== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Schaut so aus als wäre das Problem, daß ich einen globalen Filehandle
FILE* verwende, ich vermute mal der sollte nur in dem angeschubsten
Prozess gültig sein. Ich baue das halt um ;(

Die Ausgabe habe ich gekürzt, sonst kommt formerror_text_long

von Joachim D. (Firma: JDCC) (scheppertreiber)


Lesenswert?

Ja, das war's ;)

Die Meldung ist weg.

Vielen Dank nochmal an alle ! Ich hatte ein file dessen FILE*
global definiert war, in dem neuen Prozess geöffnet und später
dann wieder geschlossen. Das mußte krachen ...

Was das tcache2 damit zu tun hat ? Ich liebe C

von Marco H. (damarco)


Lesenswert?

So was macht man nicht selbst sondern verwendet Bibliotheken. Dann kann 
man sich mit der Anwendung beschäftigen und nicht mit Elementaren Kram. 
Der dir so wie es ausschaut sehr früh auf die Füße fällt.

https://www.gnu.org/software/libmicrohttpd/manual/libmicrohttpd.html

Als Beispiel welches sehr brauchbar ist....

: Bearbeitet durch User
von Georg A. (georga)


Lesenswert?

Marco H. schrieb:
> So was macht man nicht selbst sondern verwendet Bibliotheken. Dann kann
> man sich mit der Anwendung beschäftigen und nicht mit Elementaren Kram.
> Der dir so wie es ausschaut sehr früh auf die Füße fällt.

... und dann implodiert einem halt das Framework irgendwann und man hat 
keine Ahnung, wie man überhaupt rausfindet, wo und warum das sein 
könnte. Wenn man selber Unfug macht, muss das ja nicht unbedingt im 
eigenen Code auffallen.

Nene, so ein eigenes Gebastel und das Lernen von Grundlagen des 
Debuggings sind schon wichtig.

von Joachim D. (Firma: JDCC) (scheppertreiber)


Lesenswert?

Georg,

ich sehe das auch so.

Ich bin mit diesen "Fertig-all-inclusive-Sachen" schon ziemlich auf
die Schnauze geflogen. Nicht nochmal ;)

von MaWin (Gast)


Lesenswert?

Georg A. schrieb:
> Nene, so ein eigenes Gebastel und das Lernen von Grundlagen des
> Debuggings sind schon wichtig.

Ja, ist schon wichtig die remote-exploitable Schwachstelle selbst 
einzubauen, wenn man sich seinen eigenen Webserver in C bastelt.
1
char    http_header[4096];
2
...
3
appsprintf( http_header, "%s\n", str);

Ob da ein Überlauf möglich ist? Keine Ahnung. Aber es riecht stark nach 
Fisch.

von Nop (Gast)


Lesenswert?

Joachim D. schrieb:
> Alle free() in allen Dateien sind
> durch eine Abfrage ob der pointer != NULL ist abgesichert.

Das allein reicht aber IMO nicht, weil es eine race condition gibt. 
Pointer ist ungleich null, Thread 1 kommt durch die if-Abfrage, macht 
free(), wird aber vor dem manuellen Nullen des Pointers wegscheduled. 
Thread 2 kommt deswegen auch durch die if-Abfrage, es kommt zum double 
free.

von Joachim D. (Firma: JDCC) (scheppertreiber)


Lesenswert?

MaWin schrieb:
> Ob da ein Überlauf möglich ist? Keine Ahnung. Aber es riecht stark nach
> Fisch.

Ist nur aus Testzwecken drin, fliegt später wieder raus.

von Marco H. (damarco)


Lesenswert?

Joachim D. schrieb:
> Georg,
>
> ich sehe das auch so.
>
> Ich bin mit diesen "Fertig-all-inclusive-Sachen" schon ziemlich auf
> die Schnauze geflogen. Nicht nochmal ;)

Zuerst schaut man sich den Vorschlag an, dann wird man Festellen das der 
Vorschlag gar nicht so dumm war.

HTTP ist mehr als man denkt. Sein Kram wird jenseits von GET an den 
modernen Browsers schon scheitern. Wenn die Daten größer werden und der 
Browser anfängt mit Chunks ist sein Latein sowie so zu ende. Ich sehe 
das es jetzt schon der Fall ist, wenn man nur mal so in das HTTP 
Protokoll schaut welche Anforderungen selbst bei simplen Anfragen GET 
Anfragen zu erfüllen sind.

Nur den Header auseinander zu nehmen, wird ich ohne Bibliotheken nicht 
mehr alleine aufsetzen wollen. Das mag vor 20 Jahren aufgrund von 
alternativen und der noch Einfachheit des Protokolls anderes gewesen 
sein.

Das läuft vom Ansatz her gegen den Baum, ohne die Endanwendung zu 
betrachten.

: Bearbeitet durch User
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.