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)
398 char *home_env, *logname_env, *mail_env, *shell_env, *user_env;
399 int home_len, logname_len, mail_len, shell_len, user_len;
400 static char *envp[10];
402 if ((pwd = getpwnam(username)) == NULL) {
403 perror("vserver: getpwnam error ");
407 if (setgid(pwd->pw_gid) < 0) {
408 perror("vserver: setgid error ");
412 if (setuid(pwd->pw_uid) < 0) {
413 perror("vserver: setuid error ");
417 if (chdir(pwd->pw_dir) < 0) {
418 perror("vserver: chdir error ");
422 home_len = strlen("HOME=") + strlen(pwd->pw_dir) + NULLBYTE_SIZE;
423 logname_len = strlen("LOGNAME=") + strlen(username) + NULLBYTE_SIZE;
424 mail_len = strlen("MAIL=/var/spool/mail/") + strlen(username)
426 shell_len = strlen("SHELL=") + strlen(pwd->pw_shell) + NULLBYTE_SIZE;
427 user_len = strlen("USER=") + strlen(username) + NULLBYTE_SIZE;
429 home_env = (char *)malloc(home_len);
430 logname_env = (char *)malloc(logname_len);
431 mail_env = (char *)malloc(mail_len);
432 shell_env = (char *)malloc(shell_len);
433 user_env = (char *)malloc(user_len);
435 if ((home_env == NULL) ||
436 (logname_env == NULL) ||
437 (mail_env == NULL) ||
438 (shell_env == NULL) ||
439 (user_env == NULL)) {
440 perror("vserver: malloc error ");
444 sprintf(home_env, "HOME=%s", pwd->pw_dir);
445 sprintf(logname_env, "LOGNAME=%s", username);
446 sprintf(mail_env, "MAIL=/var/spool/mail/%s", username);
447 sprintf(shell_env, "SHELL=%s", pwd->pw_shell);
448 sprintf(user_env, "USER=%s", username);
450 home_env[home_len - 1] = '\0';
451 logname_env[logname_len - 1] = '\0';
452 mail_env[mail_len - 1] = '\0';
453 shell_env[shell_len - 1] = '\0';
454 user_env[user_len - 1] = '\0';
457 envp[1] = logname_env;
463 if ((putenv(home_env) < 0) ||
464 (putenv(logname_env) < 0) ||
465 (putenv(mail_env) < 0) ||
466 (putenv(shell_env) < 0) ||
467 (putenv(user_env) < 0)) {
468 perror("vserver: putenv error ");
475 void slice_enter(char *context)
481 if ((pwd = getpwnam(context)) == NULL) {
482 fprintf(stderr,"vserver: getpwname(%s) failed",context);
486 context = (char*)malloc(strlen(pwd->pw_name)+NULLBYTE_SIZE);
488 perror("vserver: malloc failed");
491 strcpy(context,pwd->pw_name);
493 if (setuidgid_root() < 0) { /* For chroot, new_s_context */
494 fprintf(stderr,"vserver: Could not setuid/setguid to root:root\n");
498 remove_cap = get_remove_cap(context);
501 if (sandbox_chroot(uid) < 0) {
502 fprintf(stderr, "vserver: Could not chroot to vserver root\n");
506 if (sandbox_processes(uid, remove_cap) < 0) {
507 fprintf(stderr, "vserver: Could not sandbox processes in vserver\n");
512 //--------------------------------------------------------------------
514 #define DEFAULT_SHELL "/bin/sh"
516 /* Exit statuses for programs like 'env' that exec other programs.
517 EXIT_FAILURE might not be 1, so use EXIT_FAIL in such programs. */
520 EXIT_CANNOT_INVOKE = 126,
524 extern void slice_enter(char *);
525 extern void runas_slice_user(char *);
527 int main(int argc, char **argv)
529 char *context, *username, *shell;
540 if ((pwd = getpwuid(uid)) == NULL) {
541 fprintf(stderr,"vsh: getpwnam error failed for %d\n",uid);
545 context = (char*)strdup(pwd->pw_name);
547 perror("vsh: strdup failed");
551 /* enter vserver "context" */
552 slice_enter(context);
554 /* Now run as username in this context. Note that for PlanetLab's
555 vserver configuration the context name also happens to be the
556 "default" username within the vserver context.
559 runas_slice_user(username);
561 /* With the uid/gid appropriately set. Let's figure out what the
562 * shell in the vserver's /etc/passwd is for the given username.
564 if ((pwd = getpwnam(username)) == NULL) {
565 fprintf(stderr,"vsh: getpwnam error failed for %s\n",username);
569 /* Make sure pw->pw_shell is non-NULL.*/
570 if (pwd->pw_shell == NULL || pwd->pw_shell[0] == '\0') {
571 pwd->pw_shell = (char *) DEFAULT_SHELL;
574 shell = (char *)strdup(pwd->pw_shell);
576 perror("vsh: strdup failed");
580 /* Check whether 'su' or 'sshd' invoked us as a login shell or
581 not; did this above when testing argv[0]=='-'.
586 args = (char**)malloc(sizeof(char*)*(argc+2));
588 perror("vsh: malloc failed");
592 for(i=1;i<argc+1;i++) {
597 (void) execvp(shell,argv);
599 int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
603 return 0; /* shutup compiler */