1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
|
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "zdtmtst.h"
const char *test_doc = "Check dumpable flag handling (non-dumpable case)";
const char *test_author = "Filipe Brandenburger <filbranden@google.com>";
int dumpable_server(void) {
char buf[256];
int ret;
for (;;) {
ret = read(0, buf, sizeof(buf));
if (ret == 0)
break;
ret = snprintf(buf, sizeof(buf), "DUMPABLE:%d\n", prctl(PR_GET_DUMPABLE));
write(1, buf, ret);
}
return 0;
}
int get_dumpable_from_pipes(int pipe_input, int pipe_output) {
char buf[256];
int len;
long value;
char *endptr = NULL;
/* input and output are from the child's point of view. */
write(pipe_input, "GET\n", 4);
len = read(pipe_output, buf, sizeof(buf) - 1);
if (len < 0) {
pr_perror("error in parent reading from pipe");
return -1;
}
buf[len] = 0;
if (memcmp(buf, "DUMPABLE:", 9) != 0) {
pr_perror("child returned [%s]", buf);
return -1;
}
value = strtol(&buf[9], &endptr, 10);
if (!endptr || *endptr != '\n' || endptr != buf + len - 1) {
pr_perror("child returned [%s]", buf);
return -1;
}
return (int)value;
}
int main(int argc, char **argv)
{
int pipe_input[2];
int pipe_output[2];
int save_dumpable;
int dumpable;
int ret;
pid_t pid;
pid_t waited;
int status;
/*
* Check if we are being re-executed to spawn the dumpable server. This
* re-execution is what essentially causes the dumpable flag to be
* cleared since we have execute but not read permissions to the
* binary.
*/
if (getenv("DUMPABLE_SERVER"))
return dumpable_server();
/*
* Otherwise, do normal startup and spawn a dumpable server. While we
* are still running as root, chmod() the binary to give it execute but
* not read permissions, that way when we execv() it as a non-root user
* the kernel will drop our dumpable flag and reset it to the value in
* /proc/sys/fs/suid_dumpable.
*/
ret = chmod(argv[0], 0111);
if (ret < 0) {
pr_perror("error chmodding %s", argv[0]);
return 1;
}
test_init(argc, argv);
ret = pipe(pipe_input);
if (ret < 0) {
pr_perror("error creating input pipe");
return 1;
}
ret = pipe(pipe_output);
if (ret < 0) {
pr_perror("error creating output pipe");
return 1;
}
pid = fork();
if (pid < 0) {
pr_perror("error forking the dumpable server");
return 1;
}
if (pid == 0) {
/*
* Child process will execv() the dumpable server. Start by
* reopening stdin and stdout to use the pipes, then set the
* environment variable and execv() the same binary.
*/
close(0);
close(1);
ret = dup2(pipe_input[0], 0);
if (ret < 0) {
pr_perror("could not dup2 pipe into child's stdin");
return 1;
}
ret = dup2(pipe_output[1], 1);
if (ret < 0) {
pr_perror("could not dup2 pipe into child's stdout");
return 1;
}
close(pipe_output[0]);
close(pipe_output[1]);
close(pipe_input[0]);
close(pipe_input[1]);
ret = setenv("DUMPABLE_SERVER", "yes", 1);
if (ret < 0) {
pr_perror("could not set the DUMPABLE_SERVER env variable");
return 1;
}
execl(argv[0], "dumpable_server", NULL);
pr_perror("could not execv %s as a dumpable_server\nError No: %d", argv[0], errno);
return 1;
}
/*
* Parent process, write to the pipe_input socket to ask the server
* child to tell us what its dumpable flag value is on its side.
*/
close(pipe_input[0]);
close(pipe_output[1]);
save_dumpable = get_dumpable_from_pipes(pipe_input[1], pipe_output[0]);
if (save_dumpable < 0) return 1;
#ifdef DEBUG
test_msg("DEBUG: before dump: dumpable=%d\n", save_dumpable);
#endif
/* Wait for dump and restore. */
test_daemon();
test_waitsig();
dumpable = get_dumpable_from_pipes(pipe_input[1], pipe_output[0]);
if (dumpable < 0) return 1;
#ifdef DEBUG
test_msg("DEBUG: after restore: dumpable=%d\n", dumpable);
#endif
if (dumpable != save_dumpable) {
errno = 0;
fail("dumpable flag was not preserved over migration");
return 1;
}
/* Closing the pipes will terminate the child server. */
close(pipe_input[1]);
close(pipe_output[0]);
waited = wait(&status);
if (waited < 0) {
pr_perror("error calling wait on the child");
return 1;
}
errno = 0;
if (waited != pid) {
pr_perror("waited pid %d did not match child pid %d",
waited, pid);
return 1;
}
if (!WIFEXITED(status)) {
pr_perror("child dumpable server returned abnormally with status=%d",
status);
return 1;
}
if (WEXITSTATUS(status) != 0) {
pr_perror("child dumpable server returned rc=%d",
WEXITSTATUS(status));
return 1;
}
pass();
return 0;
}
|