diff options
Diffstat (limited to 'util-linux/switch_root.c')
-rw-r--r-- | util-linux/switch_root.c | 177 |
1 files changed, 155 insertions, 22 deletions
diff --git a/util-linux/switch_root.c b/util-linux/switch_root.c index 32708934e..080b05e45 100644 --- a/util-linux/switch_root.c +++ b/util-linux/switch_root.c | |||
@@ -24,22 +24,33 @@ | |||
24 | //config: * Because the Linux kernel uses rootfs internally as the starting | 24 | //config: * Because the Linux kernel uses rootfs internally as the starting |
25 | //config: and ending point for searching through the kernel's doubly linked | 25 | //config: and ending point for searching through the kernel's doubly linked |
26 | //config: list of active mount points. That's why. | 26 | //config: list of active mount points. That's why. |
27 | //config: | ||
28 | // RUN_INIT config item is in klibc-utils | ||
27 | 29 | ||
28 | //applet:IF_SWITCH_ROOT(APPLET(switch_root, BB_DIR_SBIN, BB_SUID_DROP)) | 30 | //applet:IF_SWITCH_ROOT(APPLET(switch_root, BB_DIR_SBIN, BB_SUID_DROP)) |
31 | // APPLET_ODDNAME:name main location suid_type help | ||
32 | //applet:IF_RUN_INIT( APPLET_ODDNAME(run-init, switch_root, BB_DIR_SBIN, BB_SUID_DROP, run_init)) | ||
29 | 33 | ||
30 | //kbuild:lib-$(CONFIG_SWITCH_ROOT) += switch_root.o | 34 | //kbuild:lib-$(CONFIG_SWITCH_ROOT) += switch_root.o |
31 | 35 | //kbuild:lib-$(CONFIG_RUN_INIT) += switch_root.o | |
32 | //usage:#define switch_root_trivial_usage | ||
33 | //usage: "[-c /dev/console] NEW_ROOT NEW_INIT [ARGS]" | ||
34 | //usage:#define switch_root_full_usage "\n\n" | ||
35 | //usage: "Free initramfs and switch to another root fs:\n" | ||
36 | //usage: "chroot to NEW_ROOT, delete all in /, move NEW_ROOT to /,\n" | ||
37 | //usage: "execute NEW_INIT. PID must be 1. NEW_ROOT must be a mountpoint.\n" | ||
38 | //usage: "\n -c DEV Reopen stdio to DEV after switch" | ||
39 | 36 | ||
40 | #include <sys/vfs.h> | 37 | #include <sys/vfs.h> |
41 | #include <sys/mount.h> | 38 | #include <sys/mount.h> |
39 | #if ENABLE_RUN_INIT | ||
40 | # include <sys/prctl.h> | ||
41 | # include <linux/capability.h> | ||
42 | // #include <sys/capability.h> | ||
43 | // This header is in libcap, but the functions are in libc. | ||
44 | // Comment in the header says this above capset/capget: | ||
45 | /* system calls - look to libc for function to system call mapping */ | ||
46 | extern int capset(cap_user_header_t header, cap_user_data_t data); | ||
47 | extern int capget(cap_user_header_t header, const cap_user_data_t data); | ||
48 | // so for bbox, let's just repeat the declarations. | ||
49 | // This way, libcap needs not be installed in build environment. | ||
50 | #endif | ||
51 | |||
42 | #include "libbb.h" | 52 | #include "libbb.h" |
53 | |||
43 | // Make up for header deficiencies | 54 | // Make up for header deficiencies |
44 | #ifndef RAMFS_MAGIC | 55 | #ifndef RAMFS_MAGIC |
45 | # define RAMFS_MAGIC ((unsigned)0x858458f6) | 56 | # define RAMFS_MAGIC ((unsigned)0x858458f6) |
@@ -89,17 +100,125 @@ static void delete_contents(const char *directory, dev_t rootdev) | |||
89 | } | 100 | } |
90 | } | 101 | } |
91 | 102 | ||
103 | #if ENABLE_RUN_INIT | ||
104 | DEFINE_STRUCT_CAPS; | ||
105 | |||
106 | static void drop_capset(int cap_idx) | ||
107 | { | ||
108 | struct caps caps; | ||
109 | |||
110 | getcaps(&caps); | ||
111 | caps.data[CAP_TO_INDEX(cap_idx)].inheritable &= ~CAP_TO_MASK(cap_idx); | ||
112 | if (capset(&caps.header, caps.data) != 0) | ||
113 | bb_perror_msg_and_die("capset"); | ||
114 | } | ||
115 | |||
116 | static void drop_bounding_set(int cap_idx) | ||
117 | { | ||
118 | int ret; | ||
119 | |||
120 | ret = prctl(PR_CAPBSET_READ, cap_idx, 0, 0, 0); | ||
121 | if (ret < 0) | ||
122 | bb_perror_msg_and_die("prctl: %s", "PR_CAPBSET_READ"); | ||
123 | |||
124 | if (ret == 1) { | ||
125 | ret = prctl(PR_CAPBSET_DROP, cap_idx, 0, 0, 0); | ||
126 | if (ret != 0) | ||
127 | bb_perror_msg_and_die("prctl: %s", "PR_CAPBSET_DROP"); | ||
128 | } | ||
129 | } | ||
130 | |||
131 | static void drop_usermodehelper(const char *filename, int cap_idx) | ||
132 | { | ||
133 | unsigned lo, hi; | ||
134 | char buf[sizeof(int)*3 * 2 + 8]; | ||
135 | int fd; | ||
136 | int ret; | ||
137 | |||
138 | ret = open_read_close(filename, buf, sizeof(buf) - 1); | ||
139 | if (ret < 0) | ||
140 | return; /* assuming files do not exist */ | ||
141 | |||
142 | buf[ret] = '\0'; | ||
143 | ret = sscanf(buf, "%u %u", &lo, &hi); | ||
144 | if (ret != 2) | ||
145 | bb_perror_msg_and_die("can't parse file '%s'", filename); | ||
146 | |||
147 | if (cap_idx < 32) | ||
148 | lo &= ~(1 << cap_idx); | ||
149 | else | ||
150 | hi &= ~(1 << (cap_idx - 32)); | ||
151 | |||
152 | fd = xopen(filename, O_WRONLY); | ||
153 | fdprintf(fd, "%u %u", lo, hi); | ||
154 | close(fd); | ||
155 | } | ||
156 | |||
157 | static void drop_capabilities(char *string) | ||
158 | { | ||
159 | char *cap; | ||
160 | |||
161 | cap = strtok(string, ","); | ||
162 | while (cap) { | ||
163 | unsigned cap_idx; | ||
164 | |||
165 | cap_idx = cap_name_to_number(cap); | ||
166 | drop_usermodehelper("/proc/sys/kernel/usermodehelper/bset", cap_idx); | ||
167 | drop_usermodehelper("/proc/sys/kernel/usermodehelper/inheritable", cap_idx); | ||
168 | drop_bounding_set(cap_idx); | ||
169 | drop_capset(cap_idx); | ||
170 | bb_error_msg("dropped capability: %s", cap); | ||
171 | cap = strtok(NULL, ","); | ||
172 | } | ||
173 | } | ||
174 | #endif | ||
175 | |||
92 | int switch_root_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | 176 | int switch_root_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
93 | int switch_root_main(int argc UNUSED_PARAM, char **argv) | 177 | int switch_root_main(int argc UNUSED_PARAM, char **argv) |
94 | { | 178 | { |
95 | char *newroot, *console = NULL; | 179 | char *newroot, *console = NULL; |
96 | struct stat st; | 180 | struct stat st; |
97 | struct statfs stfs; | 181 | struct statfs stfs; |
182 | unsigned dry_run = 0; | ||
98 | dev_t rootdev; | 183 | dev_t rootdev; |
99 | 184 | ||
100 | // Parse args (-c console) | 185 | // Parse args. '+': stop at first non-option |
101 | opt_complementary = "-2"; // minimum 2 params | 186 | if (ENABLE_SWITCH_ROOT && (!ENABLE_RUN_INIT || applet_name[0] == 's')) { |
102 | getopt32(argv, "+c:", &console); // '+': stop at first non-option | 187 | //usage:#define switch_root_trivial_usage |
188 | //usage: "[-c CONSOLE_DEV] NEW_ROOT NEW_INIT [ARGS]" | ||
189 | //usage:#define switch_root_full_usage "\n\n" | ||
190 | //usage: "Free initramfs and switch to another root fs:\n" | ||
191 | //usage: "chroot to NEW_ROOT, delete all in /, move NEW_ROOT to /,\n" | ||
192 | //usage: "execute NEW_INIT. PID must be 1. NEW_ROOT must be a mountpoint.\n" | ||
193 | //usage: "\n -c DEV Reopen stdio to DEV after switch" | ||
194 | getopt32(argv, "^+" | ||
195 | "c:" | ||
196 | "\0" "-2" /* minimum 2 args */, | ||
197 | &console | ||
198 | ); | ||
199 | } else { | ||
200 | #if ENABLE_RUN_INIT | ||
201 | //usage:#define run_init_trivial_usage | ||
202 | //usage: "[-d CAP,CAP...] [-n] [-c CONSOLE_DEV] NEW_ROOT NEW_INIT [ARGS]" | ||
203 | //usage:#define run_init_full_usage "\n\n" | ||
204 | //usage: "Free initramfs and switch to another root fs:\n" | ||
205 | //usage: "chroot to NEW_ROOT, delete all in /, move NEW_ROOT to /,\n" | ||
206 | //usage: "execute NEW_INIT. PID must be 1. NEW_ROOT must be a mountpoint.\n" | ||
207 | //usage: "\n -c DEV Reopen stdio to DEV after switch" | ||
208 | //usage: "\n -d CAPS Drop capabilities" | ||
209 | //usage: "\n -n Dry run" | ||
210 | char *cap_list = NULL; | ||
211 | dry_run = getopt32(argv, "^+" | ||
212 | "c:d:n" | ||
213 | "\0" "-2" /* minimum 2 args */, | ||
214 | &console, | ||
215 | &cap_list | ||
216 | ); | ||
217 | dry_run >>= 2; // -n | ||
218 | if (cap_list) | ||
219 | drop_capabilities(cap_list); | ||
220 | #endif | ||
221 | } | ||
103 | argv += optind; | 222 | argv += optind; |
104 | newroot = *argv++; | 223 | newroot = *argv++; |
105 | 224 | ||
@@ -108,9 +227,12 @@ int switch_root_main(int argc UNUSED_PARAM, char **argv) | |||
108 | xstat("/", &st); | 227 | xstat("/", &st); |
109 | rootdev = st.st_dev; | 228 | rootdev = st.st_dev; |
110 | xstat(".", &st); | 229 | xstat(".", &st); |
111 | if (st.st_dev == rootdev || getpid() != 1) { | 230 | if (st.st_dev == rootdev) { |
112 | // Show usage, it says new root must be a mountpoint | 231 | // Show usage, it says new root must be a mountpoint |
113 | // and we must be PID 1 | 232 | bb_show_usage(); |
233 | } | ||
234 | if (!dry_run && getpid() != 1) { | ||
235 | // Show usage, it says we must be PID 1 | ||
114 | bb_show_usage(); | 236 | bb_show_usage(); |
115 | } | 237 | } |
116 | 238 | ||
@@ -118,7 +240,7 @@ int switch_root_main(int argc UNUSED_PARAM, char **argv) | |||
118 | // we mean it. I could make this a CONFIG option, but I would get email | 240 | // we mean it. I could make this a CONFIG option, but I would get email |
119 | // from all the people who WILL destroy their filesystems. | 241 | // from all the people who WILL destroy their filesystems. |
120 | if (stat("/init", &st) != 0 || !S_ISREG(st.st_mode)) { | 242 | if (stat("/init", &st) != 0 || !S_ISREG(st.st_mode)) { |
121 | bb_error_msg_and_die("/init is not a regular file"); | 243 | bb_error_msg_and_die("'%s' is not a regular file", "/init"); |
122 | } | 244 | } |
123 | statfs("/", &stfs); // this never fails | 245 | statfs("/", &stfs); // this never fails |
124 | if ((unsigned)stfs.f_type != RAMFS_MAGIC | 246 | if ((unsigned)stfs.f_type != RAMFS_MAGIC |
@@ -127,13 +249,15 @@ int switch_root_main(int argc UNUSED_PARAM, char **argv) | |||
127 | bb_error_msg_and_die("root filesystem is not ramfs/tmpfs"); | 249 | bb_error_msg_and_die("root filesystem is not ramfs/tmpfs"); |
128 | } | 250 | } |
129 | 251 | ||
130 | // Zap everything out of rootdev | 252 | if (!dry_run) { |
131 | delete_contents("/", rootdev); | 253 | // Zap everything out of rootdev |
254 | delete_contents("/", rootdev); | ||
132 | 255 | ||
133 | // Overmount / with newdir and chroot into it | 256 | // Overmount / with newdir and chroot into it |
134 | if (mount(".", "/", NULL, MS_MOVE, NULL)) { | 257 | if (mount(".", "/", NULL, MS_MOVE, NULL)) { |
135 | // For example, fails when newroot is not a mountpoint | 258 | // For example, fails when newroot is not a mountpoint |
136 | bb_perror_msg_and_die("error moving root"); | 259 | bb_perror_msg_and_die("error moving root"); |
260 | } | ||
137 | } | 261 | } |
138 | xchroot("."); | 262 | xchroot("."); |
139 | // The chdir is needed to recalculate "." and ".." links | 263 | // The chdir is needed to recalculate "." and ".." links |
@@ -149,8 +273,17 @@ int switch_root_main(int argc UNUSED_PARAM, char **argv) | |||
149 | } | 273 | } |
150 | } | 274 | } |
151 | 275 | ||
152 | // Exec real init | 276 | if (dry_run) { |
153 | execv(argv[0], argv); | 277 | // Does NEW_INIT look like it can be executed? |
278 | //xstat(argv[0], &st); | ||
279 | //if (!S_ISREG(st.st_mode)) | ||
280 | // bb_perror_msg_and_die("'%s' is not a regular file", argv[0]); | ||
281 | if (access(argv[0], X_OK) == 0) | ||
282 | return 0; | ||
283 | } else { | ||
284 | // Exec NEW_INIT | ||
285 | execv(argv[0], argv); | ||
286 | } | ||
154 | bb_perror_msg_and_die("can't execute '%s'", argv[0]); | 287 | bb_perror_msg_and_die("can't execute '%s'", argv[0]); |
155 | } | 288 | } |
156 | 289 | ||