aboutsummaryrefslogtreecommitdiff
path: root/util-linux/switch_root.c
diff options
context:
space:
mode:
Diffstat (limited to 'util-linux/switch_root.c')
-rw-r--r--util-linux/switch_root.c177
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 */
46extern int capset(cap_user_header_t header, cap_user_data_t data);
47extern 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
104DEFINE_STRUCT_CAPS;
105
106static 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
116static 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
131static 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
157static 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
92int switch_root_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; 176int switch_root_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
93int switch_root_main(int argc UNUSED_PARAM, char **argv) 177int 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