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 ");
183 # define CAP_CONTEXT 29
190 // The following capabilities are normally available
191 // to vservers administrator, but are place for
193 {"CAP_CHOWN",CAP_CHOWN},
194 {"CAP_DAC_OVERRIDE",CAP_DAC_OVERRIDE},
195 {"CAP_DAC_READ_SEARCH",CAP_DAC_READ_SEARCH},
196 {"CAP_FOWNER",CAP_FOWNER},
197 {"CAP_FSETID",CAP_FSETID},
198 {"CAP_KILL",CAP_KILL},
199 {"CAP_SETGID",CAP_SETGID},
200 {"CAP_SETUID",CAP_SETUID},
201 {"CAP_SETPCAP",CAP_SETPCAP},
202 {"CAP_SYS_TTY_CONFIG",CAP_SYS_TTY_CONFIG},
203 {"CAP_LEASE",CAP_LEASE},
204 {"CAP_SYS_CHROOT",CAP_SYS_CHROOT},
206 // Those capabilities are not normally available
207 // to vservers because they are not needed and
208 // may represent a security risk
209 {"CAP_LINUX_IMMUTABLE",CAP_LINUX_IMMUTABLE},
210 {"CAP_NET_BIND_SERVICE",CAP_NET_BIND_SERVICE},
211 {"CAP_NET_BROADCAST",CAP_NET_BROADCAST},
212 {"CAP_NET_ADMIN", CAP_NET_ADMIN},
213 {"CAP_NET_RAW", CAP_NET_RAW},
214 {"CAP_IPC_LOCK", CAP_IPC_LOCK},
215 {"CAP_IPC_OWNER", CAP_IPC_OWNER},
216 {"CAP_SYS_MODULE",CAP_SYS_MODULE},
217 {"CAP_SYS_RAWIO", CAP_SYS_RAWIO},
218 {"CAP_SYS_PACCT", CAP_SYS_PACCT},
219 {"CAP_SYS_ADMIN", CAP_SYS_ADMIN},
220 {"CAP_SYS_BOOT", CAP_SYS_BOOT},
221 {"CAP_SYS_NICE", CAP_SYS_NICE},
222 {"CAP_SYS_RESOURCE",CAP_SYS_RESOURCE},
223 {"CAP_SYS_TIME", CAP_SYS_TIME},
224 {"CAP_MKNOD", CAP_MKNOD},
225 {"CAP_CONTEXT", CAP_CONTEXT},
229 #define VSERVERCONF "/etc/vservers/"
230 static unsigned get_remove_cap(char *name) {
237 remove_cap = /* NOTE: keep in sync with chcontext.c */
238 (1<<CAP_LINUX_IMMUTABLE)|
239 (1<<CAP_NET_BROADCAST)|
250 (1<<CAP_SYS_RESOURCE)|
258 * find out which capabilities to put back in by reading the conf file
261 /* construct the pathname to the conf file */
262 vserverconflen = strlen(VSERVERCONF) + strlen(name) + strlen(".conf") + NULLBYTE_SIZE;
263 vserverconf = (char *)malloc(vserverconflen);
264 sprintf(vserverconf, "%s%s.conf", VSERVERCONF, name);
266 /* open the conf file for reading */
267 fb = fopen(vserverconf,"r");
272 char buffer[1000], *p;
274 /* the conf file file exist */
275 while((p=fgets(buffer,sizeof(buffer)-1,fb))!=NULL) {
277 /* walk past leading spaces */
279 len = strnlen(buffer,sizeof(buffer)-1);
280 while(isspace((int)buffer[index])) {
288 if (buffer[index] == '#') continue;
290 /* check if it is the S_CAPS */
291 if ((p=strstr(&buffer[index],"S_CAPS"))!=NULL) {
295 /* what follows is a bunch of error
296 checking to parse the S_CAPS="..."
299 /* adjust index into buffer */
300 index+= (p-&buffer[index])+strlen("S_CAPS");
302 /* skip over whitespace */
303 while(isspace((int)buffer[index])) {
312 /* expecting to see = sign */
313 if (buffer[index++]!='=') {
318 /* skip over whitespace */
319 while(isspace((int)buffer[index])) {
328 /* expecting to see the opening " */
329 if (buffer[index]!='"') {
334 /* check to see that we are still within bounds */
342 /* search for the closing " */
343 if((p=strstr(&buffer[index],"\""))==NULL) {
348 /* ok... we should now have a bunch of
349 CAP keys words within the quotes */
351 for (j=0; tbcap[j].option != NULL; j++){
352 if ((p=strstr(buffer,tbcap[j].option))!=NULL){
353 len = strlen(tbcap[j].option);
354 if (((isspace(*(p-1))) || (*(p-1)=='"')) &&
355 ((isspace(*(p+len))) || (*(p+len)=='"'))) {
356 cap |= (1<<tbcap[j].bit);
368 /* close the conf file */
374 static int sandbox_processes(uid_t uid, unsigned remove_cap)
383 flags |= 1; /* VX_INFO_LOCK -- cannot request a new vx_id */
384 /* flags |= 4; VX_INFO_NPROC -- limit number of procs in a context */
386 if (vc_new_s_context(context,remove_cap,flags) < 0) {
387 perror("vserver: new_s_context error ");
394 void runas_slice_user(char *username)
397 char *home_env, *logname_env, *mail_env, *shell_env, *user_env;
398 int home_len, logname_len, mail_len, shell_len, user_len;
399 static char *envp[10];
401 if ((pwd = getpwnam(username)) == NULL) {
402 perror("vserver: getpwnam error ");
406 if (setgid(pwd->pw_gid) < 0) {
407 perror("vserver: setgid error ");
411 if (setuid(pwd->pw_uid) < 0) {
412 perror("vserver: setuid error ");
416 if (chdir(pwd->pw_dir) < 0) {
417 perror("vserver: chdir error ");
421 home_len = strlen("HOME=") + strlen(pwd->pw_dir) + NULLBYTE_SIZE;
422 logname_len = strlen("LOGNAME=") + strlen(username) + NULLBYTE_SIZE;
423 mail_len = strlen("MAIL=/var/spool/mail/") + strlen(username)
425 shell_len = strlen("SHELL=") + strlen(pwd->pw_shell) + NULLBYTE_SIZE;
426 user_len = strlen("USER=") + strlen(username) + NULLBYTE_SIZE;
428 home_env = (char *)malloc(home_len);
429 logname_env = (char *)malloc(logname_len);
430 mail_env = (char *)malloc(mail_len);
431 shell_env = (char *)malloc(shell_len);
432 user_env = (char *)malloc(user_len);
434 if ((home_env == NULL) ||
435 (logname_env == NULL) ||
436 (mail_env == NULL) ||
437 (shell_env == NULL) ||
438 (user_env == NULL)) {
439 perror("vserver: malloc error ");
443 sprintf(home_env, "HOME=%s", pwd->pw_dir);
444 sprintf(logname_env, "LOGNAME=%s", username);
445 sprintf(mail_env, "MAIL=/var/spool/mail/%s", username);
446 sprintf(shell_env, "SHELL=%s", pwd->pw_shell);
447 sprintf(user_env, "USER=%s", username);
449 home_env[home_len - 1] = '\0';
450 logname_env[logname_len - 1] = '\0';
451 mail_env[mail_len - 1] = '\0';
452 shell_env[shell_len - 1] = '\0';
453 user_env[user_len - 1] = '\0';
456 envp[1] = logname_env;
462 if ((putenv(home_env) < 0) ||
463 (putenv(logname_env) < 0) ||
464 (putenv(mail_env) < 0) ||
465 (putenv(shell_env) < 0) ||
466 (putenv(user_env) < 0)) {
467 perror("vserver: putenv error ");
474 void slice_enter(char *context)
480 if ((pwd = getpwnam(context)) == NULL) {
481 fprintf(stderr,"vserver: getpwname(%s) failed",context);
485 context = (char*)malloc(strlen(pwd->pw_name)+NULLBYTE_SIZE);
487 perror("vserver: malloc failed");
490 strcpy(context,pwd->pw_name);
492 if (setuidgid_root() < 0) { /* For chroot, new_s_context */
493 fprintf(stderr,"vserver: Could not setuid/setguid to root:root\n");
497 remove_cap = get_remove_cap(context);
500 if (sandbox_chroot(uid) < 0) {
501 fprintf(stderr, "vserver: Could not chroot to vserver root\n");
505 if (sandbox_processes(uid, remove_cap) < 0) {
506 fprintf(stderr, "vserver: Could not sandbox processes in vserver\n");
511 //--------------------------------------------------------------------
513 #define DEFAULT_SHELL "/bin/sh"
515 /* Exit statuses for programs like 'env' that exec other programs.
516 EXIT_FAILURE might not be 1, so use EXIT_FAIL in such programs. */
519 EXIT_CANNOT_INVOKE = 126,
523 extern void slice_enter(char *);
524 extern void runas_slice_user(char *);
526 int main(int argc, char **argv)
528 char *context, *username, *shell;
539 if ((pwd = getpwuid(uid)) == NULL) {
540 fprintf(stderr,"vsh: getpwnam error failed for %d\n",uid);
544 context = (char*)strdup(pwd->pw_name);
546 perror("vsh: strdup failed");
550 /* enter vserver "context" */
551 slice_enter(context);
553 /* Now run as username in this context. Note that for PlanetLab's
554 vserver configuration the context name also happens to be the
555 "default" username within the vserver context.
558 runas_slice_user(username);
560 /* With the uid/gid appropriately set. Let's figure out what the
561 * shell in the vserver's /etc/passwd is for the given username.
563 if ((pwd = getpwnam(username)) == NULL) {
564 fprintf(stderr,"vsh: getpwnam error failed for %s\n",username);
568 /* Make sure pw->pw_shell is non-NULL.*/
569 if (pwd->pw_shell == NULL || pwd->pw_shell[0] == '\0') {
570 pwd->pw_shell = (char *) DEFAULT_SHELL;
573 shell = (char *)strdup(pwd->pw_shell);
575 perror("vsh: strdup failed");
579 /* Check whether 'su' or 'sshd' invoked us as a login shell or
580 not; did this above when testing argv[0]=='-'.
585 args = (char**)malloc(sizeof(char*)*(argc+2));
587 perror("vsh: malloc failed");
591 for(i=1;i<argc+1;i++) {
596 (void) execvp(shell,argv);
598 int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
602 return 0; /* shutup compiler */