Absturz wegen cEvent::title == NULL - Warum???

  • Es ist mir jetzt schon mehrmals passiert, dass beim Aufruf von "All events - all channels" (Tasten Grün-0-0-0) der VDR angestürzt ist. Der Backtrace besagte, dass strcmp() mit cEvent::title == NULL aufgerufen wurde. Da es nur zwei Stellen gibt, an denen neue Events angelegt werden (eit.c und epg.c) und an beiden Stellen (zumindest soweit ich das sehe) dafür gesorgt wird, dass cEvent::title auf "No title" gesetzt wird, falls es NULL ist - in eit.c über FixEpgBugs(), in epg.c explizit über SetTitle("No title"). Auch scheint immer richtig gelockt zu werden.

    Ich habe jetzt als Workaround mal

    eingebaut. Damit läuft es stabil und die Debug-Ausgabe kommt tatsächlich ab und zu.

    Da ich selber nicht erkennen kann, warum cEvent::title NULL sein kann, bitte ich euch, da vielleicht mal mit draufzuschauen. Vielleicht findet die Schwarmintelligenz ja, was ich nicht sehe.

  • Bin mit dem Review noch nicht ganz durch, aber könnte das hier passieren?

    Code
    void cEvent::SetTitle(const char *Title)
    {
      title = strcpyrealloc(title, Title);
    }

    Wenn Title == NULL, dann kommt strcpyrealloc ebenfalls mit NULL zurück (else-Zweig):

    Ob SetTitle irgendwo einmal mit NULL aufgerufen wird, konnte ich aus Zeitgründen noch nicht überprüfen… und muss an dieser Stelle kurz unterbrechen. Aber vielleicht weist es ja schon in die richtige Richtung.

    Hardware: Antec NSK2480, Asus P8B75-M LX, Intel Core i5-3570T, 4 GB RAM, NVIDIA GT610, TT-Premium S2-6400, 128 GB SSD, 14 TB HDD, Pioneer BDR-207EBK
    Software: Ubuntu 22.04 LTS mit Kernel 6.8 und VDR 2.7.3 (mit offiziellen und eigenen Patches)
    Plugins: devstatus, dvbhddevice, dvd, dvdswitch, epgsearch, extrecmenu, recsearch, femon, live, markad, mlist, osdteletext, remote, screenshot, skinenigmang, skinnopacity, streamdev, systeminfo, undelete, xineliboutput
    Addons: VDR Convert 0.1.0 (angepasst)

  • Es gibt eine solche Stelle, in eit.c(396): EpgHandlers.SetTitle(pEvent, NULL);

    Aber danach kommt ja auf jeden Fall der Aufruf von EpgHandlers.FixEpgBugs(pEvent), und dort gibt es ja

    Code
    void cEvent::FixEpgBugs(void)
    {
      if (isempty(title)) {
         // we don't want any "(null)" titles
         title = strcpyrealloc(title, tr("No title"));
         EpgBugFixStat(12, ChannelID());
         }
      ...
  • Oh, sowas ähnliches habe ich auch beobachtet und dachte es liegt an meinem EPG-Plugin. Dort ist dann beim Event keine ChannelID eingetragen (die ich mir zum Debuggen im Skin ausgebe) und in cTimer::ToDescr() gibt es einen SIGSEGV.

    Bemerkt habe ich es beim Programmieren eines Timers, wenn der externe Event durch einen DVB-Event ersetzt wird (der externe EPG reicht weiter in die Zukunft).

    Während des Ersetzens ist Schedules zwar gelockt, aber meine Vermutung (Vorsicht: reine Spkulation!) ist, dass es mit cListGarbageCollector zu tun hat, aber außer Zeit fehlt mir monmentan auch ein Ansatz wie man das Debuggen kann.

    Es kann natürlich auch Zufall sein, dass ich gerade dann einen Event angesehen habe, nachdem das Problem durch etwas anderes verursacht wurde...

  • Ich würde mal sagen, in eit.c

    Code
             pEvent = newEvent = new cEvent(SiEitEvent.getEventId());                                                                                    
             newEvent->SetStartTime(StartTime);                                                                                                          
             newEvent->SetDuration(Duration);                                                                                                            
             if (!handledExternally)                                                                                                                     
                pSchedule->AddEvent(newEvent);                                                                                                           
             }

    Das Event mit title == NULL ist jetzt im Schedule. Und dann ist es eine Frage des Timings.

    Wird dieses Event zuerst verwendet (mit title == NULL), oder wird zuerst

    Quote

    EpgHandlers.SetTitle(pEvent, ShortEventDescriptor->name.getText(buffer, sizeof(buffer)));

    ...

    EpgHandlers.FixEpgBugs(pEvent);

    aufgerufen?

    Client1: ASUS P5QC, Dual Core 3G, Cine S2, Ext. Board von TBE, Xubuntu 20.04, VDR 2.6x

    Client2: RPI3

    Server: RPI4, Sundtek SkyTV Dual 2x

  • Ich vermute einmal (was Klaus aber noch bestätigen sollte), dass alle Event-Strings immer mit isempty geprüft werden. Wäre es nicht dann nicht resilienter, im Initializer von cEvent alle Strings gleich mit dem leeren String zu initialisieren?

    Code
      title = "";
      shortText = "";
      description = "";

    Die meisten Compiler schaffen es inzwischen ja, allen drei Zeigern die gleichen Adresse zuzuweisen. Und selbst falls nicht, wäre der Speichermehrbedarf minimal.

    Und müssen wir der potentiellen Race Conditions dann trotzdem noch nachgehen?

    PS: Vermutlich müsste man doch den Code daraufhin durchsuchen, wo Konstrukte wie title ? title : default verwendet werden und stattdessen konsequent auf !isempty(title) ? title : default umstellen. Auch nicht ohne Risiko… :/

    Hardware: Antec NSK2480, Asus P8B75-M LX, Intel Core i5-3570T, 4 GB RAM, NVIDIA GT610, TT-Premium S2-6400, 128 GB SSD, 14 TB HDD, Pioneer BDR-207EBK
    Software: Ubuntu 22.04 LTS mit Kernel 6.8 und VDR 2.7.3 (mit offiziellen und eigenen Patches)
    Plugins: devstatus, dvbhddevice, dvd, dvdswitch, epgsearch, extrecmenu, recsearch, femon, live, markad, mlist, osdteletext, remote, screenshot, skinenigmang, skinnopacity, streamdev, systeminfo, undelete, xineliboutput
    Addons: VDR Convert 0.1.0 (angepasst)

  • im Initializer von cEvent alle Strings gleich mit dem leeren String zu initialisieren?

    Code
      title = "";
      shortText = "";
      description = "";

    Ziemlich schlechte Idee :( . Diverse Methoden in VDR verlassen sich darauf, dass Pointer mit malloc alloziert wurden, z.B. strcpyrealloc .

    Client1: ASUS P5QC, Dual Core 3G, Cine S2, Ext. Board von TBE, Xubuntu 20.04, VDR 2.6x

    Client2: RPI3

    Server: RPI4, Sundtek SkyTV Dual 2x

  • Ups… dann müssen wir also doch die Race conditions beleuchten.

    Hardware: Antec NSK2480, Asus P8B75-M LX, Intel Core i5-3570T, 4 GB RAM, NVIDIA GT610, TT-Premium S2-6400, 128 GB SSD, 14 TB HDD, Pioneer BDR-207EBK
    Software: Ubuntu 22.04 LTS mit Kernel 6.8 und VDR 2.7.3 (mit offiziellen und eigenen Patches)
    Plugins: devstatus, dvbhddevice, dvd, dvdswitch, epgsearch, extrecmenu, recsearch, femon, live, markad, mlist, osdteletext, remote, screenshot, skinenigmang, skinnopacity, streamdev, systeminfo, undelete, xineliboutput
    Addons: VDR Convert 0.1.0 (angepasst)

  • Ist euch eigentlich aufgefallen, dass wir mit den beiden von mir zitierten Code-Abschnitten:

    … eine – zumindest potentielle – "Use-after-free"-Vulnerability haben?

    Denn expandiert entspricht dies folgendem Code:

    Erinnern wir uns daran, dass realloc auch einen ganz anderen Pointer als den ursprünglichen zurückliefern kann:

    Quote

    The realloc() function changes the size of the memory block pointed to by ptr to size bytes. The contents will be unchanged in the range from the start of the region up to the minimum of the old and new sizes. If the new size is larger than the old size, the added memory will not be initialized. If ptr is NULL, then the call is equivalent to malloc(size), for all values of size; if size is equal to zero, and ptr is not NULL, then the call is equivalent to free(ptr). Unless ptr is NULL, it must have been returned by an earlier call to malloc(), calloc() or realloc(). If the area pointed to was moved, a free(ptr) is done.

    … ist das Ganze ist nur dann unkritisch, wenn alle Patterns der Form:

    Code
    attribute = strcpyrealloc(attribute, newValue);

    … immer sauber mit Semaphoren gesichert sind, sodass die Abfrage eine Attributs nicht während seines Updates erfolgen kann.

    Solche Patterns treten im Code massenhaft auf (ich habe 20 Stellen gefunden) und ich kann leider nicht überblicken, ob an allen Stellen immer Locks zur Anwendung kommen. Das müsste kls beurteilen.

    Hardware: Antec NSK2480, Asus P8B75-M LX, Intel Core i5-3570T, 4 GB RAM, NVIDIA GT610, TT-Premium S2-6400, 128 GB SSD, 14 TB HDD, Pioneer BDR-207EBK
    Software: Ubuntu 22.04 LTS mit Kernel 6.8 und VDR 2.7.3 (mit offiziellen und eigenen Patches)
    Plugins: devstatus, dvbhddevice, dvd, dvdswitch, epgsearch, extrecmenu, recsearch, femon, live, markad, mlist, osdteletext, remote, screenshot, skinenigmang, skinnopacity, streamdev, systeminfo, undelete, xineliboutput
    Addons: VDR Convert 0.1.0 (angepasst)

    Edited once, last by SHofmann (December 1, 2024 at 5:50 PM).

  • Es fehlten tatsächlich an zwei Stellen Locks in menu.c:

    Zum Problem wurde das womöglich durch den Sender

    ORF SPORT+;ORF:11243:HC56M2S0:S19.2E:22000:2210=2:0:0:648,650,D95,D98:13221:1:1003:0

    der im EPG sehr viele Events ohne Title hat. Mit den zusätzlichen Locks konnte ich keinen Absturz mehr beobachten.

  • Super, danke dir. Hast du bei der Gelegenheit auch gleich geprüft, ob bei allen anderen Aufrufen dieser Funktion:

    … Zugriffe auf die Attribute während der Ausführung sauber per Locks entkoppelt sind?

    Und für die Plugins können wir ja leider an dieser Stelle keine Aussage treffen. Da muss jeder Plugin-Entwickler selbst prüfen, ob:

    • bei Zugriffen alle Locks korrekt gesetzt
    • die Daten nur als Kopie und nicht mit ihrem Zeiger gecacht

    … werden. Sind noch andere kritische Szenarien denkbar?

    Hardware: Antec NSK2480, Asus P8B75-M LX, Intel Core i5-3570T, 4 GB RAM, NVIDIA GT610, TT-Premium S2-6400, 128 GB SSD, 14 TB HDD, Pioneer BDR-207EBK
    Software: Ubuntu 22.04 LTS mit Kernel 6.8 und VDR 2.7.3 (mit offiziellen und eigenen Patches)
    Plugins: devstatus, dvbhddevice, dvd, dvdswitch, epgsearch, extrecmenu, recsearch, femon, live, markad, mlist, osdteletext, remote, screenshot, skinenigmang, skinnopacity, streamdev, systeminfo, undelete, xineliboutput
    Addons: VDR Convert 0.1.0 (angepasst)

  • Ich habe exemplarisch mal ein wenig in Live herumgestöbert und frage mich, ob zum Beispiel hier:

    … nicht auch noch ein LOCK_SCHEDULES_READ gebräucht würde.

    Klaus, vielleicht eine kurze Hilfestellung für mich als VDR-Programmier-Newbie, der mit den folgenden Feinheiten nicht vertraut ist? Die History besagt:

    Heißt das, dass man keine Locks setzen muss, solange man den zugeordneten Pointer (Timers, Channels, Schedules, Recordings) im Code nicht benötigt?

    Andererseits besagt die History:

    Quote

    If a plugin needs to access several of the global lists in parallel, locking must

    always be done in the sequence Timers, Channels, Recordings, Schedules. This is

    necessary to make sure that different threads that need to lock several lists at

    the same time don't end up in a deadlock.

    Welche indirekte Abhängigkeiten gibt es diesbezüglich? Wie etwa die folgenden:

    • Muss man die Channels (Sender) immer zusammen mit ihren Schedules (EPG-Daten) sperren, obwohl man nur über die Schedules iterieren möchte.
    • Und müsste man den Schedule locken, wenn man über die Channels auf einen Schedule (wie in Zeile 12 oben) zugreifen möchte?
    • Und wie stellt sich das Ganze dar, wenn gleichzeitig noch Timer mit dazukommen?

    Fragen über Fragen…

    Hardware: Antec NSK2480, Asus P8B75-M LX, Intel Core i5-3570T, 4 GB RAM, NVIDIA GT610, TT-Premium S2-6400, 128 GB SSD, 14 TB HDD, Pioneer BDR-207EBK
    Software: Ubuntu 22.04 LTS mit Kernel 6.8 und VDR 2.7.3 (mit offiziellen und eigenen Patches)
    Plugins: devstatus, dvbhddevice, dvd, dvdswitch, epgsearch, extrecmenu, recsearch, femon, live, markad, mlist, osdteletext, remote, screenshot, skinenigmang, skinnopacity, streamdev, systeminfo, undelete, xineliboutput
    Addons: VDR Convert 0.1.0 (angepasst)

  • Die Stellen in tools.c/.h fallen ja schon mal weg ;-).

    In menu.c ist 'data' eine lokale Kopie, und ausserdem wird ein Write-Lock auf Channels gehalten.

    Ich hatte für die Fehlersuche in cEvent::Title() und cEvent::SetTitle() jeweils eine Abfrage eingebaut, ob zu dem Zeitpunkt der jeweilige Thread einen Read- bzw. Write-Lock auf Schedules hat. Das war aber eine eher "krude" Angelegenheit, da ich dazu einen Pointer auf Schedules brauchte und Members von pthread_rwlock_t abfragen musste, die plattformabhängig sind (auf Raspberry Pi anders als auf Linux gcc). Das offiziell einzubauen würde ich gerne vermeiden.

  • Wie sieht es mit dem realloc() in den beiden cString::Append() in tools.c aus?

    Es gibt noch viel mehr davon:

    Auch wenn die meisten davon vermutlich eher unkritisch sind, sollte man sie bei dieser Gelegenheit wohl besser doch auf potentielle Race conditions bzw. Use-after-free-Szenarien überprüfen.

    Hardware: Antec NSK2480, Asus P8B75-M LX, Intel Core i5-3570T, 4 GB RAM, NVIDIA GT610, TT-Premium S2-6400, 128 GB SSD, 14 TB HDD, Pioneer BDR-207EBK
    Software: Ubuntu 22.04 LTS mit Kernel 6.8 und VDR 2.7.3 (mit offiziellen und eigenen Patches)
    Plugins: devstatus, dvbhddevice, dvd, dvdswitch, epgsearch, extrecmenu, recsearch, femon, live, markad, mlist, osdteletext, remote, screenshot, skinenigmang, skinnopacity, streamdev, systeminfo, undelete, xineliboutput
    Addons: VDR Convert 0.1.0 (angepasst)

Participate now!

Don’t have an account yet? Register yourself now and be a part of our community!