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;
211 #define VSERVERCONF "/etc/vservers/"
212 static void get_limits(char *context, int *cpu, int *mem, int *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;
292 int cpu = VC_LIM_KEEP;
293 int mem = VC_LIM_KEEP;
294 int task = VC_LIM_KEEP;
295 get_limits(context,&cpu, &mem, &task);
296 (void) (sandbox_chroot(xid));
298 caps.ccaps = ~vc_get_insecureccaps();
300 caps.bcaps = ~vc_get_insecurebcaps();
303 flags.flagword = VC_VXF_INFO_LOCK;
304 flags.mask = VC_VXF_STATE_SETUP | VC_VXF_INFO_LOCK;
306 ctx = vc_ctx_create(xid);
307 if (ctx == VC_NOCTX && errno != EEXIST) {
308 PERROR("vc_ctx_create(%d)", xid);
312 /* (re)set the various limits on the (possibly new) context */
318 limits.min = VC_LIM_KEEP;
319 limits.soft = VC_LIM_KEEP;
321 if (vc_set_rlimit(xid, RLIMIT_RSS, &limits)) {
322 PERROR("vc_set_rlimit(%d, %d, %d/%d/%d)",
323 xid, RLIMIT_RSS, limits.min, limits.soft, limits.hard);
328 limits.min = VC_LIM_KEEP;
329 limits.soft = VC_LIM_KEEP;
331 if (vc_set_rlimit(xid, RLIMIT_NPROC, &limits)) {
332 PERROR("vc_set_rlimit(%d, %d, %d/%d/%d)",
333 xid, RLIMIT_NPROC, limits.min, limits.soft, limits.hard);
337 if (vc_set_ccaps(xid, &caps) == -1) {
338 PERROR("vc_set_ccaps(%d, 0x%16ullx/0x%16ullx, 0x%16ullx/0x%16ullx)",
339 xid, caps.ccaps, caps.cmask, caps.bcaps, caps.bmask);
343 if (vc_set_cflags(xid, &flags) == -1) {
344 PERROR("vc_set_cflags(%d, 0x%16llx/0x%16llx)",
345 xid, flags.flagword, flags.mask);
349 /* context already exists, migrate to it */
350 if (ctx == VC_NOCTX && vc_ctx_migrate(xid) == -1) {
351 PERROR("vc_ctx_migrate(%d)", xid);
359 void runas_slice_user(char *username)
361 struct passwd pwdd, *pwd = &pwdd, *result;
363 char *home_env, *logname_env, *mail_env, *shell_env, *user_env;
364 int home_len, logname_len, mail_len, shell_len, user_len;
366 static char *envp[10];
369 pwdBuffer_len = sysconf(_SC_GETPW_R_SIZE_MAX);
370 if (pwdBuffer_len == -1) {
371 PERROR("sysconf(_SC_GETPW_R_SIZE_MAX)");
375 pwdBuffer = (char*)malloc(pwdBuffer_len);
376 if (pwdBuffer == NULL) {
377 PERROR("malloc(%d)", pwdBuffer_len);
382 if ((getpwnam_r(username,pwd,pwdBuffer,pwdBuffer_len, &result) != 0) || (errno != 0)) {
383 PERROR("getpwnam_r(%s)", username);
387 if (setgid(pwd->pw_gid) < 0) {
388 PERROR("setgid(%d)", pwd->pw_gid);
392 if (setuid(pwd->pw_uid) < 0) {
393 PERROR("setuid(%d)", pwd->pw_uid);
397 if (chdir(pwd->pw_dir) < 0) {
398 PERROR("chdir(%s)", pwd->pw_dir);
402 home_len = strlen("HOME=") + strlen(pwd->pw_dir) + NULLBYTE_SIZE;
403 logname_len = strlen("LOGNAME=") + strlen(username) + NULLBYTE_SIZE;
404 mail_len = strlen("MAIL=/var/spool/mail/") + strlen(username)
406 shell_len = strlen("SHELL=") + strlen(pwd->pw_shell) + NULLBYTE_SIZE;
407 user_len = strlen("USER=") + strlen(username) + NULLBYTE_SIZE;
409 home_env = (char *)malloc(home_len);
410 logname_env = (char *)malloc(logname_len);
411 mail_env = (char *)malloc(mail_len);
412 shell_env = (char *)malloc(shell_len);
413 user_env = (char *)malloc(user_len);
415 if ((home_env == NULL) ||
416 (logname_env == NULL) ||
417 (mail_env == NULL) ||
418 (shell_env == NULL) ||
419 (user_env == NULL)) {
424 sprintf(home_env, "HOME=%s", pwd->pw_dir);
425 sprintf(logname_env, "LOGNAME=%s", username);
426 sprintf(mail_env, "MAIL=/var/spool/mail/%s", username);
427 sprintf(shell_env, "SHELL=%s", pwd->pw_shell);
428 sprintf(user_env, "USER=%s", username);
430 home_env[home_len - 1] = '\0';
431 logname_env[logname_len - 1] = '\0';
432 mail_env[mail_len - 1] = '\0';
433 shell_env[shell_len - 1] = '\0';
434 user_env[user_len - 1] = '\0';
437 envp[1] = logname_env;
443 if ((putenv(home_env) < 0) ||
444 (putenv(logname_env) < 0) ||
445 (putenv(mail_env) < 0) ||
446 (putenv(shell_env) < 0) ||
447 (putenv(user_env) < 0)) {
448 PERROR("vserver: putenv error ");
453 void slice_enter(char *context)
455 struct passwd pwdd, *pwd = &pwdd, *result;
460 pwdBuffer_len = sysconf(_SC_GETPW_R_SIZE_MAX);
461 if (pwdBuffer_len == -1) {
462 PERROR("sysconf(_SC_GETPW_R_SIZE_MAX)");
466 pwdBuffer = (char*)malloc(pwdBuffer_len);
467 if (pwdBuffer == NULL) {
468 PERROR("malloc(%d)", pwdBuffer_len);
473 if ((getpwnam_r(context,pwd,pwdBuffer,pwdBuffer_len, &result) != 0) || (errno != 0)) {
474 PERROR("getpwnam_r(%s)", context);
479 if (setuidgid_root() < 0) { /* For chroot, new_s_context */
480 fprintf(stderr, "vsh: Could not become root, check that SUID flag is set on binary\n");
484 #ifdef CONFIG_VSERVER_LEGACY
485 (void) (sandbox_chroot(uid));
488 if (sandbox_processes((xid_t) uid, context) < 0) {
489 fprintf(stderr, "vsh: Could not change context to %d\n", uid);
494 //--------------------------------------------------------------------
496 #define DEFAULT_SHELL "/bin/sh"
498 /* Exit statuses for programs like 'env' that exec other programs.
499 EXIT_FAILURE might not be 1, so use EXIT_FAIL in such programs. */
502 EXIT_CANNOT_INVOKE = 126,
506 int main(int argc, char **argv)
508 struct passwd pwdd, *pwd = &pwdd, *result;
509 char *context, *username, *shell, *pwdBuffer;
520 if ((pwd = getpwuid(uid)) == NULL) {
521 PERROR("getpwuid(%d)", uid);
525 context = (char*)strdup(pwd->pw_name);
531 /* enter vserver "context" */
532 slice_enter(context);
534 /* Now run as username in this context. Note that for PlanetLab's
535 vserver configuration the context name also happens to be the
536 "default" username within the vserver context.
539 runas_slice_user(username);
541 /* With the uid/gid appropriately set. Let's figure out what the
542 * shell in the vserver's /etc/passwd is for the given username.
545 pwdBuffer_len = sysconf(_SC_GETPW_R_SIZE_MAX);
546 if (pwdBuffer_len == -1) {
547 PERROR("sysconf(_SC_GETPW_R_SIZE_MAX");
550 pwdBuffer = (char*)malloc(pwdBuffer_len);
551 if (pwdBuffer == NULL) {
552 PERROR("malloc(%d)", pwdBuffer_len);
557 if ((getpwnam_r(username,pwd,pwdBuffer,pwdBuffer_len, &result) != 0) || (errno != 0)) {
558 PERROR("getpwnam_r(%s)", username);
562 /* Make sure pw->pw_shell is non-NULL.*/
563 if (pwd->pw_shell == NULL || pwd->pw_shell[0] == '\0') {
564 pwd->pw_shell = (char *) DEFAULT_SHELL;
567 shell = (char *)strdup(pwd->pw_shell);
573 /* Check whether 'su' or 'sshd' invoked us as a login shell or
574 not; did this above when testing argv[0]=='-'.
579 args = (char**)malloc(sizeof(char*)*(argc+2));
581 PERROR("malloc(%d)", sizeof(char*)*(argc+2));
586 for(i=1;i<argc+1;i++) {
591 (void) execvp(shell,argv);
593 int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
597 return 0; /* shutup compiler */