How I Found a $113,337 AF_ALG Linux Local Privilege Escalation Before Copy Fail

idnsec.com · 2026-09-29 · by Long Nguyen ·generic
A retrospective on CVE-2025-39964, an AF_ALG race condition I found in 2025 before Copy Fail drew attention to the same subsystem. I explain the out-of-bounds scatterlist access, the usercopy oracle, and the exploit that achieved root and a Docker container escape.

How I Found a $113,337 AF_ALG Linux Local Privilege Escalation Before Copy Fail

We are new. Follow our LinkedIn and X for future research and updates.

ENIDDARKLIGHT

Contact

ENIDDARKLIGHT

Contact

Summary

  • CVE-2025-39964 is an AF\_ALG race condition where two writers can leave ctx->merge true while the final scatterlist has cur equal to zero.
  • This state makes the kernel access sg\[-1\]. Controlled metadata in the preceding heap object provides a usercopy oracle and an arbitrary kernel write.
  • The exploit overwrites core\_pattern to gain root and escape a Docker container, while the upstream patch prevents concurrent writes to the same AF\_ALG socket.

In 2025, I found an AF\_ALG vulnerability in the Linux kernel that allowed an ordinary user to escalate privileges to root. This is a retrospective on how I found CVE-2025-39964 and how we developed the exploit, before Copy Fail drew wider attention to AF\_ALG in 2026.

Editorial (IDNSEC): Muhammad Alifa Ramdhan discovered CVE-2025-39964 while conducting research at STAR Labs. Credit also goes to his colleague Bing-Jhong Billy Jheng, who helped complete the exploit chain. Ramdhan wrote this article for publication on IDNSEC. The vulnerability was responsibly disclosed to Linux kernel maintainers and used as a Google kernelCTF submission, earning a $113,337.00 USD reward.

AF\_ALG is a Linux kernel feature that exposes an API for cryptographic encryption and decryption. A userspace program interacts with the API, and the kernel performs the requested operation.

The vulnerability occurs while AF\_ALG handles input from a userspace program. By understanding how that mechanism works, an out-of-bounds access can be exploited to turn an ordinary user into root. The same vulnerability can also be used to escape a Docker container and obtain root on the host. As it turned out, the vulnerable code had been present in Linux since around 2011.

Linux Kernel Attack Surface

Readers may remember Copy Fail, disclosed in 2026 and later recognized alongside DirtyFrag for Best Privilege Escalation Bug at the 2026 Pwnie Awards. It also uses AF\_ALG, but the bug in this article is different. Copy Fail is a straight-line logic flaw in the AEAD path, while CVE-2025-39964 is a race between writers sharing an AF\_ALG socket. I found this race while reviewing the source around September 2025, before Copy Fail was disclosed.

I work in vulnerability research and spend a great deal of time looking for vulnerabilities in operating systems, including the Linux kernel. At the time, my goal was to use the finding for kernelCTF, a Google program that rewards researchers who can demonstrate an LPE exploit against the latest stable Linux kernel.

To develop a Linux kernel LPE exploit, a researcher first has to audit code and subsystems that form the kernel's attack surface. For example, the well-known Dirty COW vulnerability was in the `mm` subsystem, while Dirty Pipe was found in `fs/pipe`.

While looking for the next interesting attack surface to audit, I found AF\_ALG. The first thing that caught my attention in its documentation was that AF\_ALG can be reached directly from unprivileged userspace through the socket API. From a kernel exploitation perspective, this is attractive because no privilege or special configuration is required to reach the kernel code. What made it even more interesting to me was that, as far as I could determine, no previous kernelCTF submission had used a vulnerability in AF\_ALG as its entry point.

Interacting with AF\_ALG

To interact with AF\_ALG, a userspace program calls \`socket(2)\` to obtain an AF\_ALG socket file descriptor and `bind(2)` to select an algorithm. The following selects AES-CBC through the AF\_ALG API.

int tfmfd = socket(AF_ALG, SOCK_SEQPACKET, 0); struct sockaddr_alg sa = { .salg_family = AF_ALG, .salg_type = "skcipher", / symmetric key cipher / .salg_name = "cbc(aes)", / AES in CBC mode / }; bind(tfmfd, (struct sockaddr *)&sa, sizeof(sa));

For AES-CBC, the kernel also needs an AES key, which can be set with `setsockopt()`.

unsigned char key[32] = {0}; / 256 bit key / setsockopt(tfmfd, SOL_ALG, ALG_SET_KEY, key, sizeof(key));

After setting the key, the program calls `accept()`, which returns another file descriptor ready for encryption or decryption operations.

int opfd = accept(tfmfd, NULL, 0);

The program performs an encryption or decryption operation by calling `sendmsg()` on `opfd`. In the message header's control field, it can supply the IV and the requested operation, along with the bytes to process.

char cbuf[CMSG_SPACE(sizeof(__u32)) + CMSG_SPACE(sizeof(struct af_alg_iv) + 16)] = {0};

struct iovec iov = { .iov_base = buf, .iov_len = 0x1000, };

struct msghdr msgh = { .msg_iov = &iov, .msg_iovlen = 1, .msg_control = cbuf, .msg_controllen = sizeof(cbuf), };

struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msgh); cmsg->cmsg_level = SOL_ALG; cmsg->cmsg_type = ALG_SET_OP; cmsg->cmsg_len = CMSG_LEN(sizeof(__u32)); (__u32 )CMSG_DATA(cmsg) = ALG_OP_ENCRYPT;

cmsg = CMSG_NXTHDR(&msgh, cmsg); cmsg->cmsg_level = SOL_ALG; cmsg->cmsg_type = ALG_SET_IV; cmsg->cmsg_len = CMSG_LEN(sizeof(struct af_alg_iv) + 16);

struct af_alg_iv alg_iv = (void )CMSG_DATA(cmsg); alg_iv->ivlen = 16; memset(alg_iv->iv, 0x01, 16);

ssize_t n = sendmsg(opfd, &msgh, MSG_MORE);

This first `sendmsg()` call initializes the IV, selects encryption with `ALG_OP_ENCRYPT`, and supplies `0x1000` bytes for the kernel to process. The `MSG_MORE` flag tells the kernel that the input is not yet complete, so the program may call `sendmsg()` again and append more data to the previous input. When a later `sendmsg()` call omits `MSG_MORE`, the kernel stops waiting for additional input. The program can then call `read()`, `recv()`, or `recvmsg()` on `opfd` to request the encryption and obtain its result.

AF\_ALG Internals

I use Linux kernel v6.12.44 as the reference for this analysis because it was the version on which I performed the bug hunting and exploit development. The source discussed below is available in \`crypto/af\_alg.c\`, \`crypto/algif\_skcipher.c\`, and \`include/crypto/if\_alg.h\`.

Before going deeper into the source, there is one important point to understand. Calling `sendmsg()` on AF\_ALG does not immediately perform the encryption. The kernel first collects the user-supplied data in a TX scatter-gather list. Only when the user calls `recvmsg()` does the kernel create a crypto request and use the previously collected data as input.

The flow can be simplified as follows:

AF\_ALG data flow

For an `skcipher` algorithm, the initial `sendmsg()` handler is `skcipher_sendmsg()`. It obtains the IV size for the selected cipher and forwards the entire input-handling process to `af_alg_sendmsg()`.

unsigned int ivsize = crypto_skcipher_ivsize(tfm); return af_alg_sendmsg(sock, msg, size, ivsize);

This means that most of the machinery that receives user input, allocates buffers, and retains state across `sendmsg()` calls resides in this function:

int af_alg_sendmsg(struct socket *sock, struct msghdr *msg, size_t size, unsigned int ivsize)

The `msg` parameter holds the user data and control messages, `size` is the length of the input, and `ivsize` is the IV size required by the selected cipher. AES-CBC uses a 16-byte IV regardless of whether the key is AES-128, AES-192, or AES-256.

At the beginning of the function, AF\_ALG processes the control messages through `af_alg_cmsg_send()`. This is where the kernel obtains information such as `ALG_OP_ENCRYPT`, `ALG_OP_DECRYPT`, the IV, and the associated-data length for AEAD. After validating this metadata, AF\_ALG begins placing the user data into buffers owned by the socket.

The Context Attached to `opfd`

Each `opfd` has a context of type `struct af_alg_ctx`. This context retains the data and operation state even when the program calls `sendmsg()` multiple times.

The object relationship can be simplified as follows:

Object relationship on opfd

`struct af_alg_ctx` has many fields, but only a few are necessary to understand this vulnerability:

| Field | Purpose | | ---------- | ------------------------------------------------------------------------------------- | | tsgl\_list | Linked list containing the TX scatter-gather lists for user input | | used | Total number of input bytes currently stored by the kernel | | more | Indicates that the user will send more data through MSG\_MORE | | merge | Indicates that the last page has spare space and the next input can be appended to it | | init | Indicates that the operation metadata has been initialized | | enc | Selects whether the operation is encryption or decryption |

These are not local variables that disappear when `sendmsg()` returns. They remain in the shared context for as long as the `opfd` is in use. The outcome of one `sendmsg()` call therefore affects how the next call is handled.

How the Input Is Stored

The user data is not stored in one large contiguous buffer. AF\_ALG divides it across memory pages, with each portion represented by a `struct scatterlist`.

For example, 10 KB of input on a system with 4 KB pages can be stored as follows:

Splitting input across memory pages

Each scatterlist entry records the page, the starting offset within that page, and the data length. The kernel does not need a physically contiguous 10 KB region because the Crypto API can consume the list of locations.

The scatterlist entries are grouped in `struct af_alg_tsgl` objects:

struct af_alg_tsgl { struct list_head list; unsigned int cur; struct scatterlist sg[]; };

The `sg` field is the scatterlist array, while `cur` records how many entries are populated. If `cur` is 3, the valid entries are `sg[0]`, `sg[1]`, and `sg[2]`. The last scatterlist currently in use is therefore obtained with:

sg = sgl->sg + sgl->cur - 1;

Under normal conditions, with `cur = 3`, this expression returns `sg[2]`. This simple calculation becomes an important part of the vulnerability.

A single `struct af_alg_tsgl` can hold only a limited number of entries, determined by `MAX_SGL_ENTS`. When the final object's array is full, `af_alg_alloc_tsgl()` allocates a new `af_alg_tsgl`, initializes its `cur` to 0, and appends it to `ctx->tsgl_list`.

The data retained by the context therefore looks roughly like this:

The tsgl\_list structure

Processing Inside `af_alg_sendmsg()`

After processing the control messages, `af_alg_sendmsg()` loops while input remains to be copied from the user. There are two broad paths.

If the last page is full, or there is no page available, the kernel allocates a new page. It then copies data with `memcpy_from_msg()`, initializes the scatterlist entry, increments `sgl->cur`, and adds the number of successfully copied bytes to `ctx->used`.

If the last page is not full, AF\_ALG does not need to allocate a new one immediately. The remaining space can hold data from the next `sendmsg()` call. This condition is recorded in `ctx->merge`.

Suppose the user sends only 2 KB into a page with a 4 KB capacity:

| Contents of the last page | Size | | ------------------------- | ---------- | | Data already copied | 2048 bytes | | Remaining space | 2048 bytes | | Total PAGE\_SIZE | 4096 bytes |

Because space remains, `ctx->merge` becomes true. The next `sendmsg()` call enters this branch:

if (ctx->merge) { sgl = list_entry(ctx->tsgl_list.prev, struct af_alg_tsgl, list); sg = sgl->sg + sgl->cur - 1; / append data into the last page / }

In this context, `ctx->merge = true` means that the next data can be appended to the final scatterlist entry.

After the copy, AF\_ALG records whether the call used `MSG_MORE` in `ctx->more`. A true value means the user has not finished the input for this operation. A later `sendmsg()` call can continue the same input without resending all of the metadata.

The normal `af_alg_sendmsg()` flow can be summarized as follows:

Normal af\_alg\_sendmsg flow

This is how AF\_ALG accepts input across several calls without allocating a fresh page for every small addition.

The merge branch assumes that the last `af_alg_tsgl` has at least one entry to append to. I wrote the condition as follows.

ctx->merge == true => last_sgl->cur > 0

If this condition holds, `sgl->cur - 1` is safe because `cur` cannot be zero. I wanted to test whether it still held when two threads called `sendmsg()` on the same `opfd`.

When Two Threads Call `sendmsg()` Concurrently

`af_alg_sendmsg()` calls `lock_sock()` before modifying the context. At first glance, this appears to serialize multiple `sendmsg()` calls. When the send buffer is full, however, the function can enter `af_alg_wait_for_wmem()` and wait for space to become available.

Inside `af_alg_wait_for_wmem()`, the wait is performed through the `sk_wait_event()` macro:

if (sk_wait_event(sk, &timeout, af_alg_writable(sk), &wait)) { err = 0; break; }

The socket unlock and relock are not directly visible when reading only that function. They live inside the implementation of \`sk\_wait\_event()\`. Reduced to the relevant operations, the macro performs:

release_sock(__sk); / wait_woken() while the condition is false / lock_sock(__sk); / evaluate the condition again /

While a thread sleeps waiting for the send buffer to become writable, the socket lock is deliberately released. This is necessary so another operation can consume data and free space. When the thread wakes, it reacquires the lock before `af_alg_sendmsg()` continues.

As a result, two threads can both have unfinished `sendmsg()` calls on the same `opfd`. They do not modify the context at exactly the same time because the socket lock still protects it. The second thread can nevertheless wake up to a different context state from the one it saw before waiting.

I used the following initial condition:

last_sgl->cur = MAX_SGL_ENTS - 1 ctx->merge = false send buffer = full

Two threads then call `sendmsg()` and both block in `af_alg_wait_for_wmem()`. After some buffered data is released, the first thread wakes. The final `af_alg_tsgl` still has one free entry, so this thread uses it. I make the copied input shorter than `PAGE_SIZE`, leaving the final page partially empty and setting `ctx->merge` to true.

In the following diagram, `tail.cur` denotes the `cur` field of the final `af_alg_tsgl`. The field is not stored directly in `af_alg_ctx`, and the final object is reached through the last entry in `ctx->tsgl_list`.

The two-thread interleaving looks like this:

Two writers and the changing af\_alg\_ctx state

After Thread A finishes, but before Thread B continues, the state is:

last_sgl->cur = MAX_SGL_ENTS ctx->merge = true

The second thread is still on the wait path. Once buffer space becomes available, it resumes after the writable-memory check. Because the final `af_alg_tsgl` is now full, `af_alg_alloc_tsgl()` allocates a new object and appends it to `tsgl_list`.

The new object begins with:

new_last_sgl->cur = 0

For the second thread, I provide an invalid userspace address so that `memcpy_from_msg()` fails. The newly allocated page is released and the function exits through its error path. The new `af_alg_tsgl` nevertheless remains the final list entry, while the `ctx->merge` value created by the first thread remains true.

For the first time, the context reaches this state:

ctx->merge = true last_sgl->cur = 0

This is the state that previously appeared impossible.

On the next `sendmsg()` call, `ctx->merge` makes the kernel retrieve the final scatterlist entry:

sg = sgl->sg + sgl->cur - 1;

Because `sgl->cur` is zero, the calculation becomes:

sg = sgl->sg + 0 - 1 = &sgl->sg[-1]

The pointer no longer refers to an element in the `sg[]` array. It points to memory immediately before the array. When `memcpy_from_msg()` uses fields from this fake scatterlist, it produces the out-of-bounds access at the root of CVE-2025-39964.

What I find interesting is that the line performing `sgl->cur - 1` is correct as long as the earlier invariant always holds. The bug is not simply a missing check on one user-controlled input. It exists because the interleaving of two threads and an error path allows shared state to enter a combination the code never expected.

At this point, I could produce a KASAN crash:

BUG: KASAN: slab-out-of-bounds in af_alg_sendmsg Read of size 8 by task exploit

A crash does not automatically mean that the vulnerability can provide root access. The next questions were: what memory lies immediately before `sg[]`, how much of it can be controlled, and can this out-of-bounds access be converted into a useful exploitation primitive?

From Out-of-Bounds Access to Arbitrary Write

After obtaining the KASAN crash, the first thing I needed to understand was the position of `sg[-1]` relative to the object in use. On the kernel and configuration I exploited, the two structures were laid out as follows:

struct af_alg_tsgl offset 0x00: list_head (16 bytes) offset 0x10: cur (4 bytes) offset 0x14: padding (4 bytes) offset 0x18: sg[0]

sizeof(struct scatterlist) = 0x20

Because `sg[0]` starts at offset `0x18`, the position of `sg[-1]` is:

offset sg[0] - sizeof(struct scatterlist) = 0x18 - 0x20 = -0x8

In other words, the fake scatterlist begins eight bytes before the `af_alg_tsgl` object.

Each `af_alg_tsgl` is allocated as a 4096-byte object. If the allocator places another object immediately before the vulnerable `af_alg_tsgl`, `sg[-1].page_link` reads the final eight bytes of that preceding object, at offset `0xff8`.

| Position | Interpreted as | | -------------------------------------------- | ----------------------------------- | | Previous object + 0xff8 | sg\[-1\].page\_link | | First eight bytes of af\_alg\_tsgl.list.next | sg\[-1\].offset and sg\[-1\].length | | af\_alg\_tsgl object + 0x18 | The real sg\[0\] |

This is where the out-of-bounds access begins to become useful. I heap-spray allocations of the same size and fill the end of the preceding object with controlled data. The goal is to make the eight bytes at offset `0xff8` become the value that the kernel later reads as `sg[-1].page_link`.

The `page_link` field in `struct scatterlist` is used to obtain a `struct page`. The vulnerable path then computes the copy destination approximately as follows:

dest = page_address(sg_page(sg)) + sg->offset + sg->length; memcpy_from_msg(dest, msg, len);

Controlling `sg[-1].page_link` lets us influence the page returned by `sg_page(sg)`. But `sg[-1].offset` and `sg[-1].length` are read from the `list.next` pointer at the beginning of the current `af_alg_tsgl`. They reflect a linked-list address rather than values we supplied in the sprayed `page_link`. Those fields also contribute to the destination, so setting `page_link` alone does not tell us the exact address that receives the copy.

Conceptually, the exploit progresses as follows:

CVE-2025-39964 exploit progression

In practice, turning control over `page_link` into an arbitrary write is not as simple as filling it with the address of `core_pattern`. The kernel uses `struct page` and vmemmap addresses, while KASLR prevents an unprivileged user from knowing those addresses directly.

The Usercopy Property Behind `memcpy_from_msg()`

Credit for this usercopy trick and for finishing the exploit chain goes to my coworker Bing-Jhong Billy Jheng. He noticed that the `memcpy_from_msg()` path gave us a way to probe the computed destination even though the linked-list pointer contributes to `sg[-1].offset` and `sg[-1].length`.

At first, trying many possible `page_link` values sounds dangerous. If the computed address is invalid and the kernel uses an ordinary `memcpy()`, one incorrect guess could cause a kernel page fault and crash the system. Such an oracle would not be very useful because every failed attempt would require a kernel restart.

This vulnerable path does not use an ordinary `memcpy()`. It writes through `memcpy_from_msg()`:

err = memcpy_from_msg(page_address(sg_page(sg)) + sg->offset + sg->length, msg, len);

The implementation of \`memcpy\_from\_msg()\` forwards the copy to `copy_from_iter_full()`:

static inline int memcpy_from_msg(void *data, struct msghdr *msg, int len) { return copy_from_iter_full(data, len, &msg->msg_iter) ? 0 : -EFAULT; }

Because the data in `msg_iter` originates in userspace, this path eventually reaches \`copy\_from\_user\_iter()\`, which calls `raw_copy_from_user()` on Linux v6.12.44:

if (access_ok(iter_from, len)) { to += progress; res = raw_copy_from_user(to, iter_from, len); }

`access_ok()` checks the userspace source address. It does not check the destination `to`, which in this case comes from `sg[-1]`.

On the x86-64 target I used, \`raw\_copy\_from\_user()\` uses a routine with exception handling for faults at either end of the copy. It also permits SMAP access during usercopy. When `to` points to a writable userspace page, the copy succeeds. When it points to an unmapped address, the exception handler returns the number of bytes left uncopied instead of causing a kernel oops.

`memcpy_from_msg()` converts an incomplete copy into `-EFAULT`. From userspace, the result provides an oracle.

| Computed destination | Copy result | sendmsg() result | Kernel state | | -------------------------------- | -------------------------- | -------------------------------- | ------------- | | Falls on a mapped userspace page | Succeeds | Returns the number of bytes sent | Keeps running | | Falls on an unmapped address | Usercopy catches the fault | Fails with EFAULT | Keeps running |

We can therefore keep testing guesses without crashing the kernel on each miss. The PoC copies one byte. `sendmsg()` returns `1` for a valid destination, or `-1` with `errno == EFAULT` for an invalid one.

We then map a large virtual address range in userspace and vary `page_link` in a controlled way. The oracle tells us whether the resulting copy lands on a mapped page without crashing on misses. Narrowing down that mapped destination reveals the page, and the observed copy identifies its offset despite the contribution from `list.next`. With that position known, the exploit accounts for the within-page offset and shifts `page_link` toward the kernel page containing `core_pattern`.

The full kernelCTF exploit write-up for CVE-2025-39964 covers the address mapping, oracle-based search, `mincore()` checks, and the calculation needed to reach the target page.

For this article, the most important point is the relationship between the bug and the resulting primitive:

`sg[-1]` makes the kernel read `page_link` from a controllable heap object. That value is then used to compute the destination passed to `memcpy_from_msg()`, converting an out-of-bounds metadata read into a kernel write primitive.

Why Target `core_pattern`?

`core_pattern` controls how the kernel handles a core dump. If its value begins with a pipe character (`|`), the kernel executes the program specified after it and sends the core dump to that program.

Using the arbitrary write, the exploit replaces `core_pattern` with a command that points back to the exploit binary. It then deliberately crashes a child process. The kernel executes that binary as the core-dump handler with root privileges.

In the kernelCTF environment I targeted, this primitive provided more than a local privilege escalation from an ordinary user to root. Because AF\_ALG can be reached from inside a container and the exploited kernel belongs to the host, the same path can escape a Docker container and obtain root on the host.

I chose to explain the exploit to this depth because readers still need to understand why the crash is exploitable. Details such as each spray size, the allocator grooming order, address-range values, and binary-search calculations would bury the discovery story in implementation detail. Readers who want to reproduce the exploit can follow those parts more directly in `exploit.md` and the PoC source.

How the Bug Was Fixed

The vulnerable skcipher code can be traced back to commit \`8ff590903d5f\`, `crypto: algif_skcipher - User-space interface for skcipher operations`, which shipped with Linux 2.6.38 in 2011. Affected kernels remained vulnerable until the fix in 2025, subject to vendor backports and configuration. The bug had been in this code for about 14 years when I found it.

The main problem was not merely the absence of a `cur == 0` check. That condition was a consequence of two writers sharing one `af_alg_ctx` and resuming their operations after the context state changed along the wait path.

The upstream patch therefore fixes the problem by giving a writer exclusive ownership. While one `sendmsg()` is in progress, another writer on the same socket receives `-EBUSY`:

lock_sock(sk); if (ctx->write) { release_sock(sk); return -EBUSY; } ctx->write = true;

Before the function returns, it sets `ctx->write` back to false. The socket lock may still be released while waiting for memory, but another thread can no longer begin a second write against the same context.

I think this fix also explains the root cause very clearly. The old implementation implicitly assumed that only one writer was building the input. The patch turns that assumption into a rule the kernel actually enforces. The complete change is available in commit \`crypto: af\_alg - Disallow concurrent writes in af\_alg\_sendmsg\`.

The following demo runs the local privilege escalation exploit in the kernelCTF environment. The exploit triggers the AF\_ALG race condition, constructs an arbitrary kernel write, and obtains root access from an unprivileged user.

kernelCTF LPE exploit demo

The kernelCTF submission earned a reward of $113,337.00 USD after the vulnerability's impact was demonstrated with a working LPE exploit against the kernelCTF target.

Closing: Finding a Vulnerability and Building an Exploit

The finished exploit can make the work look straightforward. My notes tell a messier story. I spent time understanding the code and testing the race before I had a useful crash, then spent much longer turning that crash into a reliable exploit.

The research began long before there was a KASAN crash. I had to choose an attack surface reachable by an unprivileged user, understand how AF\_ALG stored its input, and build a model of the state that should always remain valid. From there, the first question was not “how do I get root?” but “does `merge` really always mean that a final SG entry exists?”

The KASAN crash gave me a starting point for the exploit. Triggering the race consistently, laying out heap objects, and controlling `page_link` took further work. Billy's observation about the usercopy path gave us an oracle: a bad destination could return `EFAULT` while leaving the kernel running. We used it to locate the copy before directing the write toward `core_pattern`.

Both parts mattered. The source review exposed an assumption about the shared state. The exploit work showed how breaking that assumption could lead to a reproducible root compromise. I wanted to share the steps between those two points, because the path from a suspicious line of code to a working exploit is where much of the research happens.

idnsec.com · 13 images · 22 links

Comments (0)

No comments yet