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_BIND_SERVICE)|
240 (1<<CAP_NET_BROADCAST)|
251 (1<<CAP_SYS_RESOURCE)|
259 * find out which capabilities to put back in by reading the conf file
262 /* construct the pathname to the conf file */
263 vserverconflen = strlen(VSERVERCONF) + strlen(name) + strlen(".conf") + NULLBYTE_SIZE;
264 vserverconf = (char *)malloc(vserverconflen);
265 sprintf(vserverconf, "%s%s.conf", VSERVERCONF, name);
267 /* open the conf file for reading */
268 fb = fopen(vserverconf,"r");
273 char buffer[1000], *p;
275 /* the conf file file exist */
276 while((p=fgets(buffer,sizeof(buffer)-1,fb))!=NULL) {
278 /* walk past leading spaces */
280 len = strnlen(buffer,sizeof(buffer)-1);
281 while(isspace((int)buffer[index])) {
289 if (buffer[index] == '#') continue;
291 /* check if it is the S_CAPS */
292 if ((p=strstr(&buffer[index],"S_CAPS"))!=NULL) {
296 /* what follows is a bunch of error
297 checking to parse the S_CAPS="..."
300 /* adjust index into buffer */
301 index+= (p-&buffer[index])+strlen("S_CAPS");
303 /* skip over whitespace */
304 while(isspace((int)buffer[index])) {
313 /* expecting to see = sign */
314 if (buffer[index++]!='=') {
319 /* skip over whitespace */
320 while(isspace((int)buffer[index])) {
329 /* expecting to see the opening " */
330 if (buffer[index]!='"') {
335 /* check to see that we are still within bounds */
343 /* search for the closing " */
344 if((p=strstr(&buffer[index],"\""))==NULL) {
349 /* ok... we should now have a bunch of
350 CAP keys words within the quotes */
352 for (j=0; tbcap[j].option != NULL; j++){
353 if ((p=strstr(buffer,tbcap[j].option))!=NULL){
354 len = strlen(tbcap[j].option);
355 if (((isspace(*(p-1))) || (*(p-1)=='"')) &&
356 ((isspace(*(p+len))) || (*(p+len)=='"'))) {
357 cap |= (1<<tbcap[j].bit);
369 /* close the conf file */
375 static int sandbox_processes(uid_t uid, unsigned remove_cap)
384 flags |= 1; /* VX_INFO_LOCK -- cannot request a new vx_id */
385 /* flags |= 4; VX_INFO_NPROC -- limit number of procs in a context */
387 if (vc_new_s_context(context,remove_cap,flags) < 0) {
388 perror("vserver: new_s_context error ");
395 void runas_slice_user(char *username)
397 struct passwd pwdd, *pwd = &pwdd, *result;
399 char *home_env, *logname_env, *mail_env, *shell_env, *user_env;
400 int home_len, logname_len, mail_len, shell_len, user_len;
402 static char *envp[10];
405 pwdBuffer_len = sysconf(_SC_GETPW_R_SIZE_MAX);
406 if (pwdBuffer_len == -1) {
407 perror("vserver: _SC_GETPW_R_SIZE_MAX not defined ");
411 pwdBuffer = (char*)malloc(pwdBuffer_len);
412 if (pwdBuffer == NULL) {
413 perror("vserver: malloc error ");
418 if ((getpwnam_r(username,pwd,pwdBuffer,pwdBuffer_len, &result) != 0) || (errno != 0)) {
419 perror("vserver: getpwnam error ");
423 if (setgid(pwd->pw_gid) < 0) {
424 perror("vserver: setgid error ");
428 if (setuid(pwd->pw_uid) < 0) {
429 perror("vserver: setuid error ");
433 if (chdir(pwd->pw_dir) < 0) {
434 perror("vserver: chdir error ");
438 home_len = strlen("HOME=") + strlen(pwd->pw_dir) + NULLBYTE_SIZE;
439 logname_len = strlen("LOGNAME=") + strlen(username) + NULLBYTE_SIZE;
440 mail_len = strlen("MAIL=/var/spool/mail/") + strlen(username)
442 shell_len = strlen("SHELL=") + strlen(pwd->pw_shell) + NULLBYTE_SIZE;
443 user_len = strlen("USER=") + strlen(username) + NULLBYTE_SIZE;
445 home_env = (char *)malloc(home_len);
446 logname_env = (char *)malloc(logname_len);
447 mail_env = (char *)malloc(mail_len);
448 shell_env = (char *)malloc(shell_len);
449 user_env = (char *)malloc(user_len);
451 if ((home_env == NULL) ||
452 (logname_env == NULL) ||
453 (mail_env == NULL) ||
454 (shell_env == NULL) ||
455 (user_env == NULL)) {
456 perror("vserver: malloc error ");
460 sprintf(home_env, "HOME=%s", pwd->pw_dir);
461 sprintf(logname_env, "LOGNAME=%s", username);
462 sprintf(mail_env, "MAIL=/var/spool/mail/%s", username);
463 sprintf(shell_env, "SHELL=%s", pwd->pw_shell);
464 sprintf(user_env, "USER=%s", username);
466 home_env[home_len - 1] = '\0';
467 logname_env[logname_len - 1] = '\0';
468 mail_env[mail_len - 1] = '\0';
469 shell_env[shell_len - 1] = '\0';
470 user_env[user_len - 1] = '\0';
473 envp[1] = logname_env;
479 if ((putenv(home_env) < 0) ||
480 (putenv(logname_env) < 0) ||
481 (putenv(mail_env) < 0) ||
482 (putenv(shell_env) < 0) ||
483 (putenv(user_env) < 0)) {
484 perror("vserver: putenv error ");
489 void slice_enter(char *context)
491 struct passwd pwdd, *pwd = &pwdd, *result;
497 pwdBuffer_len = sysconf(_SC_GETPW_R_SIZE_MAX);
498 if (pwdBuffer_len == -1) {
499 perror("vserver: _SC_GETPW_R_SIZE_MAX not defined ");
503 pwdBuffer = (char*)malloc(pwdBuffer_len);
504 if (pwdBuffer == NULL) {
505 perror("vserver: malloc error ");
510 if ((getpwnam_r(context,pwd,pwdBuffer,pwdBuffer_len, &result) != 0) || (errno != 0)) {
511 fprintf(stderr,"vserver: getpwname(%s) failed",context);
516 context = (char*)malloc(strlen(pwd->pw_name)+NULLBYTE_SIZE);
518 perror("vserver: malloc failed");
521 strcpy(context,pwd->pw_name);
523 if (setuidgid_root() < 0) { /* For chroot, new_s_context */
524 fprintf(stderr,"vserver: Could not setuid/setguid to root:root\n");
528 remove_cap = get_remove_cap(context);
531 if (sandbox_chroot(uid) < 0) {
532 fprintf(stderr, "vserver: Could not chroot to vserver root\n");
536 if (sandbox_processes(uid, remove_cap) < 0) {
537 fprintf(stderr, "vserver: Could not sandbox processes in vserver\n");
542 //--------------------------------------------------------------------
544 #define DEFAULT_SHELL "/bin/sh"
546 /* Exit statuses for programs like 'env' that exec other programs.
547 EXIT_FAILURE might not be 1, so use EXIT_FAIL in such programs. */
550 EXIT_CANNOT_INVOKE = 126,
554 int main(int argc, char **argv)
556 struct passwd pwdd, *pwd = &pwdd, *result;
557 char *context, *username, *shell, *pwdBuffer;
568 if ((pwd = getpwuid(uid)) == NULL) {
569 fprintf(stderr,"vsh: getpwnam error failed for %d\n",uid);
573 context = (char*)strdup(pwd->pw_name);
575 perror("vsh: strdup failed");
579 /* enter vserver "context" */
580 slice_enter(context);
582 /* Now run as username in this context. Note that for PlanetLab's
583 vserver configuration the context name also happens to be the
584 "default" username within the vserver context.
587 runas_slice_user(username);
589 /* With the uid/gid appropriately set. Let's figure out what the
590 * shell in the vserver's /etc/passwd is for the given username.
593 pwdBuffer_len = sysconf(_SC_GETPW_R_SIZE_MAX);
594 if (pwdBuffer_len == -1) {
595 perror("vserver: _SC_GETPW_R_SIZE_MAX not defined ");
598 pwdBuffer = (char*)malloc(pwdBuffer_len);
599 if (pwdBuffer == NULL) {
600 perror("vserver: malloc error ");
605 if ((getpwnam_r(username,pwd,pwdBuffer,pwdBuffer_len, &result) != 0) || (errno != 0)) {
606 fprintf(stderr,"vsh: getpwnam error failed for %s\n",username);
610 /* Make sure pw->pw_shell is non-NULL.*/
611 if (pwd->pw_shell == NULL || pwd->pw_shell[0] == '\0') {
612 pwd->pw_shell = (char *) DEFAULT_SHELL;
615 shell = (char *)strdup(pwd->pw_shell);
617 perror("vsh: strdup failed");
621 /* Check whether 'su' or 'sshd' invoked us as a login shell or
622 not; did this above when testing argv[0]=='-'.
627 args = (char**)malloc(sizeof(char*)*(argc+2));
629 perror("vsh: malloc failed");
633 for(i=1;i<argc+1;i++) {
638 (void) execvp(shell,argv);
640 int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
644 return 0; /* shutup compiler */