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>
43 //--------------------------------------------------------------------
44 #include "linuxcaps.h"
47 /* Null byte made explicit */
48 #define NULLBYTE_SIZE 1
50 /* Base for all vserver roots for chroot */
51 #define VSERVER_ROOT_BASE "/vservers"
53 /* Change to root:root (before entering new context) */
54 static int setuidgid_root()
57 fprintf(stderr, "setgid error\n");
61 fprintf(stderr, "setuid error\n");
67 static void compute_new_root(char *base, char **root, uid_t uid)
72 if ((pwd = getpwuid(uid)) == NULL) {
73 perror("vserver: getpwuid error ");
78 strlen(base) + strlen("/") +
79 strlen(pwd->pw_name) + NULLBYTE_SIZE;
80 (*root) = (char *)malloc(root_len);
81 if ((*root) == NULL) {
82 perror("vserver: malloc error ");
86 sprintf((*root), "%s/%s", base, pwd->pw_name);
87 (*root)[root_len - 1] = '\0';
90 /* Example: sandbox_root = /vservers/bnc, relpath = /proc/1 */
91 static int sandbox_file_exists(char *sandbox_root, char *relpath)
97 len = strlen(sandbox_root) + strlen(relpath) + NULLBYTE_SIZE;
98 if ((file = (char *)malloc(len)) == NULL) {
99 perror("vserver: malloc error ");
102 sprintf(file, "%s%s", sandbox_root, relpath);
103 file[len - 1] = '\0';
104 if (stat(file, &stat_buf) == 0) {
113 static int proc_mounted(char *sandbox_root)
115 return sandbox_file_exists(sandbox_root, "/proc/1");
118 static int devpts_mounted(char *sandbox_root)
120 return sandbox_file_exists(sandbox_root, "/dev/pts/0");
123 static void mount_proc(char *sandbox_root,uid_t uid)
125 char *source = "/proc";
129 len = strlen(sandbox_root) + strlen("/") + strlen("proc") + NULLBYTE_SIZE;
130 if ((target = (char *)malloc(len)) == NULL) {
131 perror("vserver: malloc error ");
135 sprintf(target, "%s/proc", sandbox_root);
136 target[len - 1] = '\0';
137 if (!proc_mounted(sandbox_root))
138 mount(source, target, "proc", MS_BIND | MS_RDONLY, NULL);
143 static void mount_devpts(char *sandbox_root)
145 char *source = "/dev/pts";
149 len = strlen(sandbox_root) + strlen("/") + strlen("dev/pts") + NULLBYTE_SIZE;
150 if ((target = (char *)malloc(len)) == NULL) {
151 perror("vserver: malloc error ");
155 sprintf(target, "%s/dev/pts", sandbox_root);
156 target[len - 1] = '\0';
157 if (!devpts_mounted(sandbox_root))
158 mount(source, target, "devpts", 0, NULL);
163 static int sandbox_chroot(uid_t uid)
165 char *sandbox_root = NULL;
167 compute_new_root(VSERVER_ROOT_BASE,&sandbox_root, uid);
168 mount_proc(sandbox_root,uid);
169 mount_devpts(sandbox_root);
170 if (chroot(sandbox_root) < 0) {
171 fprintf(stderr,"vserver: chroot error (%s): ",sandbox_root);
175 if (chdir("/") < 0) {
176 perror("vserver: chdir error ");
186 // The following capabilities are normally available
187 // to vservers administrator, but are place for
189 {"CAP_CHOWN",CAP_CHOWN},
190 {"CAP_DAC_OVERRIDE",CAP_DAC_OVERRIDE},
191 {"CAP_DAC_READ_SEARCH",CAP_DAC_READ_SEARCH},
192 {"CAP_FOWNER",CAP_FOWNER},
193 {"CAP_FSETID",CAP_FSETID},
194 {"CAP_KILL",CAP_KILL},
195 {"CAP_SETGID",CAP_SETGID},
196 {"CAP_SETUID",CAP_SETUID},
197 {"CAP_SETPCAP",CAP_SETPCAP},
198 {"CAP_SYS_TTY_CONFIG",CAP_SYS_TTY_CONFIG},
199 {"CAP_LEASE",CAP_LEASE},
200 {"CAP_SYS_CHROOT",CAP_SYS_CHROOT},
202 // Those capabilities are not normally available
203 // to vservers because they are not needed and
204 // may represent a security risk
205 {"CAP_LINUX_IMMUTABLE",CAP_LINUX_IMMUTABLE},
206 {"CAP_NET_BIND_SERVICE",CAP_NET_BIND_SERVICE},
207 {"CAP_NET_BROADCAST",CAP_NET_BROADCAST},
208 {"CAP_NET_ADMIN", CAP_NET_ADMIN},
209 {"CAP_NET_RAW", CAP_NET_RAW},
210 {"CAP_IPC_LOCK", CAP_IPC_LOCK},
211 {"CAP_IPC_OWNER", CAP_IPC_OWNER},
212 {"CAP_SYS_MODULE",CAP_SYS_MODULE},
213 {"CAP_SYS_RAWIO", CAP_SYS_RAWIO},
214 {"CAP_SYS_PACCT", CAP_SYS_PACCT},
215 {"CAP_SYS_ADMIN", CAP_SYS_ADMIN},
216 {"CAP_SYS_BOOT", CAP_SYS_BOOT},
217 {"CAP_SYS_NICE", CAP_SYS_NICE},
218 {"CAP_SYS_RESOURCE",CAP_SYS_RESOURCE},
219 {"CAP_SYS_TIME", CAP_SYS_TIME},
220 {"CAP_MKNOD", CAP_MKNOD},
222 {"CAP_QUOTACTL", CAP_QUOTACTL},
227 #define VSERVERCONF "/etc/vservers/"
228 static unsigned get_remove_cap(char *name) {
235 remove_cap = /* NOTE: keep in sync with chcontext.c */
236 (1<<CAP_LINUX_IMMUTABLE)|
237 (1<<CAP_NET_BROADCAST)|
248 (1<<CAP_SYS_RESOURCE)|
261 * find out which capabilities to put back in by reading the conf file
264 /* construct the pathname to the conf file */
265 vserverconflen = strlen(VSERVERCONF) + strlen(name) + strlen(".conf") + NULLBYTE_SIZE;
266 vserverconf = (char *)malloc(vserverconflen);
267 sprintf(vserverconf, "%s%s.conf", VSERVERCONF, name);
269 /* open the conf file for reading */
270 fb = fopen(vserverconf,"r");
275 char buffer[1000], *p;
277 /* the conf file file exist */
278 while((p=fgets(buffer,sizeof(buffer)-1,fb))!=NULL) {
280 /* walk past leading spaces */
282 len = strnlen(buffer,sizeof(buffer)-1);
283 while(isspace((int)buffer[index])) {
291 if (buffer[index] == '#') continue;
293 /* check if it is the S_CAPS */
294 if ((p=strstr(&buffer[index],"S_CAPS"))!=NULL) {
298 /* what follows is a bunch of error
299 checking to parse the S_CAPS="..."
302 /* adjust index into buffer */
303 index+= (p-&buffer[index])+strlen("S_CAPS");
305 /* skip over whitespace */
306 while(isspace((int)buffer[index])) {
315 /* expecting to see = sign */
316 if (buffer[index++]!='=') {
321 /* skip over whitespace */
322 while(isspace((int)buffer[index])) {
331 /* expecting to see the opening " */
332 if (buffer[index]!='"') {
337 /* check to see that we are still within bounds */
345 /* search for the closing " */
346 if((p=strstr(&buffer[index],"\""))==NULL) {
351 /* ok... we should now have a bunch of
352 CAP keys words within the quotes */
354 for (j=0; tbcap[j].option != NULL; j++){
355 if ((p=strstr(buffer,tbcap[j].option))!=NULL){
356 len = strlen(tbcap[j].option);
357 if (((isspace(*(p-1))) || (*(p-1)=='"')) &&
358 ((isspace(*(p+len))) || (*(p+len)=='"'))) {
359 cap |= (1<<tbcap[j].bit);
371 /* close the conf file */
377 static int sandbox_processes(uid_t uid, unsigned remove_cap)
386 flags |= 1; /* VX_INFO_LOCK -- cannot request a new vx_id */
387 /* flags |= 4; VX_INFO_NPROC -- limit number of procs in a context */
389 if (vc_new_s_context(context,remove_cap,flags) < 0) {
390 perror("vserver: new_s_context error ");
397 void runas_slice_user(char *username)
400 char *home_env, *logname_env, *mail_env, *shell_env, *user_env;
401 int home_len, logname_len, mail_len, shell_len, user_len;
402 static char *envp[10];
404 if ((pwd = getpwnam(username)) == NULL) {
405 perror("vserver: getpwnam error ");
409 if (setgid(pwd->pw_gid) < 0) {
410 perror("vserver: setgid error ");
414 if (setuid(pwd->pw_uid) < 0) {
415 perror("vserver: setuid error ");
419 if (chdir(pwd->pw_dir) < 0) {
420 perror("vserver: chdir error ");
424 home_len = strlen("HOME=") + strlen(pwd->pw_dir) + NULLBYTE_SIZE;
425 logname_len = strlen("LOGNAME=") + strlen(username) + NULLBYTE_SIZE;
426 mail_len = strlen("MAIL=/var/spool/mail/") + strlen(username)
428 shell_len = strlen("SHELL=") + strlen(pwd->pw_shell) + NULLBYTE_SIZE;
429 user_len = strlen("USER=") + strlen(username) + NULLBYTE_SIZE;
431 home_env = (char *)malloc(home_len);
432 logname_env = (char *)malloc(logname_len);
433 mail_env = (char *)malloc(mail_len);
434 shell_env = (char *)malloc(shell_len);
435 user_env = (char *)malloc(user_len);
437 if ((home_env == NULL) ||
438 (logname_env == NULL) ||
439 (mail_env == NULL) ||
440 (shell_env == NULL) ||
441 (user_env == NULL)) {
442 perror("vserver: malloc error ");
446 sprintf(home_env, "HOME=%s", pwd->pw_dir);
447 sprintf(logname_env, "LOGNAME=%s", username);
448 sprintf(mail_env, "MAIL=/var/spool/mail/%s", username);
449 sprintf(shell_env, "SHELL=%s", pwd->pw_shell);
450 sprintf(user_env, "USER=%s", username);
452 home_env[home_len - 1] = '\0';
453 logname_env[logname_len - 1] = '\0';
454 mail_env[mail_len - 1] = '\0';
455 shell_env[shell_len - 1] = '\0';
456 user_env[user_len - 1] = '\0';
459 envp[1] = logname_env;
465 if ((putenv(home_env) < 0) ||
466 (putenv(logname_env) < 0) ||
467 (putenv(mail_env) < 0) ||
468 (putenv(shell_env) < 0) ||
469 (putenv(user_env) < 0)) {
470 perror("vserver: putenv error ");
477 void slice_enter(char *context)
483 if ((pwd = getpwnam(context)) == NULL) {
484 fprintf(stderr,"vserver: getpwname(%s) failed",context);
488 context = (char*)malloc(strlen(pwd->pw_name)+NULLBYTE_SIZE);
490 perror("vserver: malloc failed");
493 strcpy(context,pwd->pw_name);
495 if (setuidgid_root() < 0) { /* For chroot, new_s_context */
496 fprintf(stderr,"vserver: Could not setuid/setguid to root:root\n");
500 remove_cap = get_remove_cap(context);
503 if (sandbox_chroot(uid) < 0) {
504 fprintf(stderr, "vserver: Could not chroot to vserver root\n");
508 if (sandbox_processes(uid, remove_cap) < 0) {
509 fprintf(stderr, "vserver: Could not sandbox processes in vserver\n");
514 //--------------------------------------------------------------------
516 #define DEFAULT_SHELL "/bin/sh"
518 /* Exit statuses for programs like 'env' that exec other programs.
519 EXIT_FAILURE might not be 1, so use EXIT_FAIL in such programs. */
522 EXIT_CANNOT_INVOKE = 126,
526 extern void slice_enter(char *);
527 extern void runas_slice_user(char *);
529 int main(int argc, char **argv)
531 char *context, *username, *shell;
542 if ((pwd = getpwuid(uid)) == NULL) {
543 fprintf(stderr,"vsh: getpwnam error failed for %d\n",uid);
547 context = (char*)strdup(pwd->pw_name);
549 perror("vsh: strdup failed");
553 /* enter vserver "context" */
554 slice_enter(context);
556 /* Now run as username in this context. Note that for PlanetLab's
557 vserver configuration the context name also happens to be the
558 "default" username within the vserver context.
561 runas_slice_user(username);
563 /* With the uid/gid appropriately set. Let's figure out what the
564 * shell in the vserver's /etc/passwd is for the given username.
566 if ((pwd = getpwnam(username)) == NULL) {
567 fprintf(stderr,"vsh: getpwnam error failed for %s\n",username);
571 /* Make sure pw->pw_shell is non-NULL.*/
572 if (pwd->pw_shell == NULL || pwd->pw_shell[0] == '\0') {
573 pwd->pw_shell = (char *) DEFAULT_SHELL;
576 shell = (char *)strdup(pwd->pw_shell);
578 perror("vsh: strdup failed");
582 /* Check whether 'su' or 'sshd' invoked us as a login shell or
583 not; did this above when testing argv[0]=='-'.
588 args = (char**)malloc(sizeof(char*)*(argc+2));
590 perror("vsh: malloc failed");
594 for(i=1;i<argc+1;i++) {
599 (void) execvp(shell,argv);
601 int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
605 return 0; /* shutup compiler */