2 * Marc E. Fiuczynski <mef@cs.princeton.edu>
4 * Copyright (c) 2004 The Trustees of Princeton University (Trustees).
6 * vsh is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2, or (at your option)
11 * vsh is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
14 * License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with Poptop; see the file COPYING. If not, write to the Free
18 * Software Foundation, 59 Temple Place - Suite 330, Boston, MA
35 #include <sys/syscall.h>
36 #include <asm/unistd.h>
37 #include <sys/mount.h>
38 #include <sys/types.h>
40 #include <sys/resource.h>
45 //--------------------------------------------------------------------
48 #undef CONFIG_VSERVER_LEGACY
50 /* Null byte made explicit */
51 #define NULLBYTE_SIZE 1
53 /* Base for all vserver roots for chroot */
54 #define VSERVER_ROOT_BASE "/vservers"
57 _PERROR(const char *format, char *file, int line, int _errno, ...)
62 fprintf(stderr, "%s:%d: ", file, line);
63 vfprintf(stderr, format, ap);
65 fprintf(stderr, ": %s (%d)", strerror(_errno), _errno);
72 #define PERROR(format, args...) _PERROR(format, __FILE__, __LINE__, errno, ## args)
74 /* Change to root:root (before entering new context) */
75 static int setuidgid_root()
88 static void compute_new_root(char *base, char **root, uid_t uid)
93 if ((pwd = getpwuid(uid)) == NULL) {
94 PERROR("getpwuid(%d)", uid);
99 strlen(base) + strlen("/") +
100 strlen(pwd->pw_name) + NULLBYTE_SIZE;
101 (*root) = (char *)malloc(root_len);
102 if ((*root) == NULL) {
103 PERROR("malloc(%d)", root_len);
107 sprintf((*root), "%s/%s", base, pwd->pw_name);
108 (*root)[root_len - 1] = '\0';
111 /* Example: sandbox_root = /vservers/bnc, relpath = /proc/1 */
112 static int sandbox_file_exists(char *sandbox_root, char *relpath)
114 struct stat stat_buf;
118 len = strlen(sandbox_root) + strlen(relpath) + NULLBYTE_SIZE;
119 if ((file = (char *)malloc(len)) == NULL) {
120 PERROR("malloc(%d)", len);
123 sprintf(file, "%s%s", sandbox_root, relpath);
124 file[len - 1] = '\0';
125 if (stat(file, &stat_buf) == 0) {
134 static int proc_mounted(char *sandbox_root)
136 return sandbox_file_exists(sandbox_root, "/proc/1");
139 static int devpts_mounted(char *sandbox_root)
141 return sandbox_file_exists(sandbox_root, "/dev/pts/0");
144 static void mount_proc(char *sandbox_root)
146 char *source = "/proc";
150 len = strlen(sandbox_root) + strlen("/") + strlen("proc") + NULLBYTE_SIZE;
151 if ((target = (char *)malloc(len)) == NULL) {
152 PERROR("malloc(%d)", len);
156 sprintf(target, "%s/proc", sandbox_root);
157 target[len - 1] = '\0';
158 if (!proc_mounted(sandbox_root))
159 mount(source, target, "proc", MS_BIND | MS_RDONLY, NULL);
164 static void mount_devpts(char *sandbox_root)
166 char *source = "/dev/pts";
170 len = strlen(sandbox_root) + strlen("/") + strlen("dev/pts") + NULLBYTE_SIZE;
171 if ((target = (char *)malloc(len)) == NULL) {
172 PERROR("malloc(%d)", len);
176 sprintf(target, "%s/dev/pts", sandbox_root);
177 target[len - 1] = '\0';
178 if (!devpts_mounted(sandbox_root))
179 mount(source, target, "devpts", 0, NULL);
184 static int sandbox_chroot(uid_t uid)
186 char *sandbox_root = NULL;
188 compute_new_root(VSERVER_ROOT_BASE,&sandbox_root, uid);
189 mount_proc(sandbox_root);
190 mount_devpts(sandbox_root);
191 if (chroot(sandbox_root) < 0) {
192 PERROR("chroot(%s)", sandbox_root);
195 if (chdir("/") < 0) {
202 #define WHITESPACE(buffer,index,len) \
203 while(isspace((int)buffer[index])) \
204 if (index < len) index++; else goto out;
208 unsigned long long *limit;
211 #define VSERVERCONF "/etc/vservers/"
212 static void get_limits(char *context, unsigned long long *cpu, unsigned long long *mem, unsigned long long *task) {
214 size_t len = strlen(VSERVERCONF) + strlen(context) + strlen(".conf") + NULLBYTE_SIZE;
215 char *conf = (char *)malloc(len);
216 struct resources list[] =
223 sprintf(conf, "%s%s.conf", VSERVERCONF, context);
225 /* open the conf file for reading */
226 fb = fopen(conf,"r");
229 char *buffer = malloc(1000);
232 /* the conf file exist */
233 while((p=fgets(buffer,1000-1,fb))!=NULL) {
235 len = strnlen(buffer,1000);
236 WHITESPACE(buffer,index,len);
237 if (buffer[index] == '#')
240 for (r=list; r->name; r++)
241 if ((p=strstr(&buffer[index],r->name))!=NULL) {
242 /* adjust index into buffer */
243 index+= (p-&buffer[index])+strlen(r->name);
245 /* skip over whitespace */
246 WHITESPACE(buffer,index,len);
248 /* expecting to see = sign */
249 if (buffer[index++]!='=') goto out;
251 /* skip over whitespace */
252 WHITESPACE(buffer,index,len);
254 /* expecting to see a digit for number */
255 if (!isdigit((int)buffer[index])) goto out;
257 *r->limit = atoi(&buffer[index]);
264 fprintf(stderr,"cannot open %s\n",conf);
270 static int sandbox_processes(xid_t xid, char *context)
272 #ifdef CONFIG_VSERVER_LEGACY
276 flags |= 1; /* VX_INFO_LOCK -- cannot request a new vx_id */
277 /* flags |= 4; VX_INFO_NPROC -- limit number of procs in a context */
279 (void) vc_new_s_context(xid, 0, flags);
281 /* use legacy dirty hack for capremove */
282 if (vc_new_s_context(VC_SAMECTX, vc_get_insecurebcaps(), flags) == VC_NOCTX) {
283 PERROR("vc_new_s_context(%u, 0x%16ullx, 0x%08x)",
284 VC_SAMECTX, vc_get_insecurebcaps(), flags);
288 struct vc_rlimit limits;
289 struct vc_ctx_caps caps;
290 struct vc_ctx_flags flags;
293 unsigned long long cpu = VC_LIM_KEEP;
294 unsigned long long mem = VC_LIM_KEEP;
295 unsigned long long task = VC_LIM_KEEP;
296 get_limits(context,&cpu, &mem, &task);
297 (void) (sandbox_chroot(xid));
299 caps.ccaps = ~vc_get_insecureccaps();
301 caps.bcaps = ~vc_get_insecurebcaps();
304 flags.flagword = VC_VXF_INFO_LOCK;
305 flags.mask = VC_VXF_STATE_SETUP | VC_VXF_INFO_LOCK;
307 ctx = vc_ctx_create(xid);
308 if (ctx == VC_NOCTX && errno != EEXIST) {
309 PERROR("vc_ctx_create(%d)", xid);
314 if (cpu != VC_LIM_KEEP) {
315 struct vc_set_sched sched = {
319 /* Need to distinguish between guarantee (hard) and
320 * best effort (share) from the vserver
323 #define VC_VXF_SCHED_SHARE 0x00000800ull
324 flags.flagword |= VC_VXF_SCHED_HARD | VC_VXF_SCHED_SHARE;
325 flags.mask |= VC_VXF_SCHED_HARD | VC_VXF_SCHED_SHARE;
327 /* CPULIMIT value from /etc/vservers/xyz.conf */
328 sched.fill_rate = cpu;
329 sched.set_mask |= VC_VXSM_FILL_RATE;
331 sched.interval = 1000; /* Andy's default value */
332 sched.set_mask |= VC_VXSM_INTERVAL;
334 /* set base token value for new contexts */
335 if (ctx != VC_NOCTX) {
336 sched.tokens = 100; /* Andy's default value */
337 sched.set_mask |= VC_VXSM_TOKENS;
340 sched.tokens_min = 50; /* Andy's default value */
341 sched.tokens_max = 100; /* Andy's default value */
342 sched.set_mask |= VC_VXSM_TOKENS_MIN;
343 sched.set_mask |= VC_VXSM_TOKENS_MAX;
345 if (vc_set_sched(xid, &sched)==-1) {
346 PERROR("vc_set_sched()");
352 limits.min = VC_LIM_KEEP;
353 limits.soft = VC_LIM_KEEP;
355 if (vc_set_rlimit(xid, RLIMIT_RSS, &limits)) {
356 PERROR("vc_set_rlimit(%d, %d, %d/%d/%d)",
357 xid, RLIMIT_RSS, limits.min, limits.soft, limits.hard);
362 limits.min = VC_LIM_KEEP;
363 limits.soft = VC_LIM_KEEP;
365 if (vc_set_rlimit(xid, RLIMIT_NPROC, &limits)) {
366 PERROR("vc_set_rlimit(%d, %d, %d/%d/%d)",
367 xid, RLIMIT_NPROC, limits.min, limits.soft, limits.hard);
371 if (vc_set_ccaps(xid, &caps) == -1) {
372 PERROR("vc_set_ccaps(%d, 0x%16ullx/0x%16ullx, 0x%16ullx/0x%16ullx)",
373 xid, caps.ccaps, caps.cmask, caps.bcaps, caps.bmask);
377 if (vc_set_cflags(xid, &flags) == -1) {
378 PERROR("vc_set_cflags(%d, 0x%16llx/0x%16llx)",
379 xid, flags.flagword, flags.mask);
383 /* context already exists, migrate to it */
384 if (ctx == VC_NOCTX && vc_ctx_migrate(xid) == -1) {
385 PERROR("vc_ctx_migrate(%d)", xid);
393 void runas_slice_user(char *username)
395 struct passwd pwdd, *pwd = &pwdd, *result;
397 char *home_env, *logname_env, *mail_env, *shell_env, *user_env;
398 int home_len, logname_len, mail_len, shell_len, user_len;
400 static char *envp[10];
403 pwdBuffer_len = sysconf(_SC_GETPW_R_SIZE_MAX);
404 if (pwdBuffer_len == -1) {
405 PERROR("sysconf(_SC_GETPW_R_SIZE_MAX)");
409 pwdBuffer = (char*)malloc(pwdBuffer_len);
410 if (pwdBuffer == NULL) {
411 PERROR("malloc(%d)", pwdBuffer_len);
416 if ((getpwnam_r(username,pwd,pwdBuffer,pwdBuffer_len, &result) != 0) || (errno != 0)) {
417 PERROR("getpwnam_r(%s)", username);
421 if (setgid(pwd->pw_gid) < 0) {
422 PERROR("setgid(%d)", pwd->pw_gid);
426 if (setuid(pwd->pw_uid) < 0) {
427 PERROR("setuid(%d)", pwd->pw_uid);
431 if (chdir(pwd->pw_dir) < 0) {
432 PERROR("chdir(%s)", pwd->pw_dir);
436 home_len = strlen("HOME=") + strlen(pwd->pw_dir) + NULLBYTE_SIZE;
437 logname_len = strlen("LOGNAME=") + strlen(username) + NULLBYTE_SIZE;
438 mail_len = strlen("MAIL=/var/spool/mail/") + strlen(username)
440 shell_len = strlen("SHELL=") + strlen(pwd->pw_shell) + NULLBYTE_SIZE;
441 user_len = strlen("USER=") + strlen(username) + NULLBYTE_SIZE;
443 home_env = (char *)malloc(home_len);
444 logname_env = (char *)malloc(logname_len);
445 mail_env = (char *)malloc(mail_len);
446 shell_env = (char *)malloc(shell_len);
447 user_env = (char *)malloc(user_len);
449 if ((home_env == NULL) ||
450 (logname_env == NULL) ||
451 (mail_env == NULL) ||
452 (shell_env == NULL) ||
453 (user_env == NULL)) {
458 sprintf(home_env, "HOME=%s", pwd->pw_dir);
459 sprintf(logname_env, "LOGNAME=%s", username);
460 sprintf(mail_env, "MAIL=/var/spool/mail/%s", username);
461 sprintf(shell_env, "SHELL=%s", pwd->pw_shell);
462 sprintf(user_env, "USER=%s", username);
464 home_env[home_len - 1] = '\0';
465 logname_env[logname_len - 1] = '\0';
466 mail_env[mail_len - 1] = '\0';
467 shell_env[shell_len - 1] = '\0';
468 user_env[user_len - 1] = '\0';
471 envp[1] = logname_env;
477 if ((putenv(home_env) < 0) ||
478 (putenv(logname_env) < 0) ||
479 (putenv(mail_env) < 0) ||
480 (putenv(shell_env) < 0) ||
481 (putenv(user_env) < 0)) {
482 PERROR("vserver: putenv error ");
487 void slice_enter(char *context)
489 struct passwd pwdd, *pwd = &pwdd, *result;
494 pwdBuffer_len = sysconf(_SC_GETPW_R_SIZE_MAX);
495 if (pwdBuffer_len == -1) {
496 PERROR("sysconf(_SC_GETPW_R_SIZE_MAX)");
500 pwdBuffer = (char*)malloc(pwdBuffer_len);
501 if (pwdBuffer == NULL) {
502 PERROR("malloc(%d)", pwdBuffer_len);
507 if ((getpwnam_r(context,pwd,pwdBuffer,pwdBuffer_len, &result) != 0) || (errno != 0)) {
508 PERROR("getpwnam_r(%s)", context);
513 if (setuidgid_root() < 0) { /* For chroot, new_s_context */
514 fprintf(stderr, "vsh: Could not become root, check that SUID flag is set on binary\n");
518 #ifdef CONFIG_VSERVER_LEGACY
519 (void) (sandbox_chroot(uid));
522 if (sandbox_processes((xid_t) uid, context) < 0) {
523 fprintf(stderr, "vsh: Could not change context to %d\n", uid);
528 //--------------------------------------------------------------------
530 #define DEFAULT_SHELL "/bin/sh"
532 /* Exit statuses for programs like 'env' that exec other programs.
533 EXIT_FAILURE might not be 1, so use EXIT_FAIL in such programs. */
536 EXIT_CANNOT_INVOKE = 126,
540 int main(int argc, char **argv)
542 struct passwd pwdd, *pwd = &pwdd, *result;
543 char *context, *username, *shell, *pwdBuffer;
554 if ((pwd = getpwuid(uid)) == NULL) {
555 PERROR("getpwuid(%d)", uid);
559 context = (char*)strdup(pwd->pw_name);
565 /* enter vserver "context" */
566 slice_enter(context);
568 /* Now run as username in this context. Note that for PlanetLab's
569 vserver configuration the context name also happens to be the
570 "default" username within the vserver context.
573 runas_slice_user(username);
575 /* With the uid/gid appropriately set. Let's figure out what the
576 * shell in the vserver's /etc/passwd is for the given username.
579 pwdBuffer_len = sysconf(_SC_GETPW_R_SIZE_MAX);
580 if (pwdBuffer_len == -1) {
581 PERROR("sysconf(_SC_GETPW_R_SIZE_MAX");
584 pwdBuffer = (char*)malloc(pwdBuffer_len);
585 if (pwdBuffer == NULL) {
586 PERROR("malloc(%d)", pwdBuffer_len);
591 if ((getpwnam_r(username,pwd,pwdBuffer,pwdBuffer_len, &result) != 0) || (errno != 0)) {
592 PERROR("getpwnam_r(%s)", username);
596 /* Make sure pw->pw_shell is non-NULL.*/
597 if (pwd->pw_shell == NULL || pwd->pw_shell[0] == '\0') {
598 pwd->pw_shell = (char *) DEFAULT_SHELL;
601 shell = (char *)strdup(pwd->pw_shell);
607 /* Check whether 'su' or 'sshd' invoked us as a login shell or
608 not; did this above when testing argv[0]=='-'.
613 args = (char**)malloc(sizeof(char*)*(argc+2));
615 PERROR("malloc(%d)", sizeof(char*)*(argc+2));
620 for(i=1;i<argc+1;i++) {
625 (void) execvp(shell,argv);
627 int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
631 return 0; /* shutup compiler */