fixed execution order of chroot(2) and context-creation; this caused
[util-vserver.git] / util-vserver / src / rpm-fake.c
1 // $Id$    --*- c++ -*--
2
3 // Copyright (C) 2003 Enrico Scholz <enrico.scholz@informatik.tu-chemnitz.de>
4 //  
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; version 2 of the License.
8 //  
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //  
14 // You should have received a copy of the GNU General Public License
15 // along with this program; if not, write to the Free Software
16 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17 //  
18
19 #ifdef HAVE_CONFIG_H
20 #  include <config.h>
21 #endif
22
23 #include "pathconfig.h"
24 #include "util.h"
25
26 #include <lib/vserver.h>
27 #include <lib/internal.h>
28 #include <lib_internal/sys_clone.h>
29
30 #include <sys/socket.h>
31 #include <dlfcn.h>
32 #include <stdlib.h>
33 #include <stdint.h>
34 #include <stdio.h>
35 #include <unistd.h>
36 #include <asm/unistd.h>
37 #include <string.h>
38 #include <stdbool.h>
39 #include <errno.h>
40 #include <fcntl.h>
41 #include <stdarg.h>
42 #include <sys/mount.h>
43 #include <linux/fs.h>
44 #include <sched.h>
45 #include <signal.h>
46 #include <sys/types.h>
47 #include <sys/wait.h>
48 #include <sys/un.h>
49 #include <fcntl.h>
50 #include <pwd.h>
51 #include <grp.h>
52
53
54   // from selinux.h
55   // FIXME: add configure autodetection and include <selinux.h> directly
56 int rpm_execcon(unsigned int verified,
57                 const char *filename,
58                 char *const argv[], char *const envp[]);
59
60
61 #define ENSC_WRAPPERS_PREFIX    "rpm-fake.so: "
62 #define ENSC_WRAPPERS_VSERVER   1
63 #define ENSC_WRAPPERS_UNISTD    1
64 #include <wrappers.h>
65
66 #ifndef CLONE_NEWNS
67 #  define CLONE_NEWNS   0x00020000
68 #endif
69
70 #define LIBNAME         "rpm-fake.so"
71 #define PLATFORM_FILE   "/etc/rpm/platform"
72
73 #define INIT(FILE,FUNC) FUNC##_func = ((__typeof__(FUNC) *) (xdlsym(FILE, #FUNC)))
74 #define DECLARE(FUNC)   static __typeof__(FUNC) *       FUNC##_func = 0
75
76
77 #define DBG_INIT        0x0001
78 #define DBG_VARIABLES   0x0002
79 #define DBG_RESOLVER    0x0004
80 #define DBG_EXECV       0x0008
81 #define DBG_VERBOSE0    0x8000
82 #define DBG_VERBOSE1    (0x4000 | DBG_VERBOSE0)
83 #define DBG_VERBOSE2    (0x2000 | DBG_VERBOSE1)
84
85 int                     wrapper_exit_code = 255;
86
87 static xid_t            ctx   = VC_NOCTX;
88 static uint32_t         caps  = ~0;
89 static int              flags = 0;
90 static char const *     mnts  = 0;
91 static char const *     root  = 0;
92 static int              pw_sock   = -1;
93 static int              sync_sock = -1;
94 static unsigned int     debug_level = 0;
95
96 static bool             is_initialized = false;
97
98   //DECLARE(rpm_execcon);
99   //DECLARE(execv);
100 DECLARE(getpwnam);
101 DECLARE(getgrnam);
102 DECLARE(endpwent);
103 DECLARE(endgrent);
104
105 static void             initRPMFake() __attribute__((__constructor__));
106 static void             exitRPMFake() __attribute__((__destructor__));
107
108 static inline bool
109 isDbgLevel(unsigned int level)
110 {
111   return ((debug_level&level)==level);
112 }
113
114 static void *
115 xdlsym(void *handle, const char *symbol)
116 {
117   void  *res = dlsym(handle, symbol);
118   if (res==0) {
119     char const  *error = dlerror();
120     write(2, symbol, strlen(symbol));
121     write(2, ": ", 2);
122     write(2, error, strlen(error));
123     write(2, "\n", 2);
124
125     _exit(255);
126   }
127
128   return res;
129 }
130
131 static void
132 showHelp()
133 {
134   WRITE_MSG(1,
135             "Usage: LD_PRELOAD=" LIBNAME " <executable> <args>*\n\n"
136             LIBNAME " unterstands the following environment variables:\n"
137             "  $RPM_FAKE_RESOLVER     ...  program which does the NSS resolving (defaults\n"
138             "                              to " RESOLVER_PROG ")\n"
139             "  $RPM_FAKE_RESOLVER_UID ...  uid of the resolver program\n"
140             "  $RPM_FAKE_RESOLVER_GID ...  gid of the resolver program\n"
141             "  $RPM_FAKE_CTX          ...  vserver context which shall be used for resolver\n"
142             "                              and scriptlets\n"
143             "  $RPM_FAKE_CAP          ...  linux capability remove-mask for the context\n"
144             "  $RPM_FAKE_FLAGS        ...  vserver flags of the context\n"
145             "  $RPM_FAKE_CHROOT       ...  directory of the chroot environment\n"
146             "  $RPM_FAKE_NAMESPACE_MOUNTS\n"
147             "                          ... colon separated list of directories which will\n"
148             "                              umounted before scriptlet execution\n\n"
149             "  $RPM_FAKE_HELP          ... shows this message\n"
150             "  $RPM_FAKE_VERSION       ... shows the version of this program\n\n"
151             "  $RPM_FAKE_DEBUG         ... sets the debuglevel bitmask\n\n"
152             "Please report bugs to " PACKAGE_BUGREPORT "\n");
153   exit(0);
154 }
155
156 static void
157 showVersion()
158 {
159   WRITE_MSG(1,
160             LIBNAME " " VERSION " -- wrapper around rpm\n"
161             "This program is part of " PACKAGE_STRING "\n\n"
162             "Copyright (C) 2003 Enrico Scholz\n"
163             VERSION_COPYRIGHT_DISCLAIMER);
164   exit(0);
165 }
166
167 static int
168 getAndClearEnv(char const *key, int dflt)
169 {
170   char          *env = getenv(key);
171   int           res;
172
173   if (env==0 || env[0]=='\0') res = dflt;
174   else {
175     res = atoi(env);
176     unsetenv(key);
177   }
178
179   return res;
180 }
181
182   /// \returns true iff we are in ctx after leaving this function
183 static bool
184 setupContext(xid_t xid, char const **xid_str)
185 {
186   bool          res = false;
187   
188   if (vc_isSupported(vcFEATURE_MIGRATE)) {
189     xid_t       rc=VC_NOCTX;
190
191     if ((xid==VC_DYNAMIC_XID || !vc_is_dynamic_xid(xid)) &&
192         (rc=vc_ctx_create(xid))==VC_NOCTX &&
193         errno!=EEXIST) {
194       perror(ENSC_WRAPPERS_PREFIX "vc_create_context()");
195       exit(255);
196     }
197
198     if (rc!=VC_NOCTX) {
199       char                      buf[sizeof(xid_t)*3 + 128];
200       size_t                    l;
201       struct vc_ctx_caps        caps;
202       
203       strcpy(buf, "rpm-fake.so #");
204       l = utilvserver_fmt_uint(buf+sizeof("rpm-fake.so #")-1, getppid());
205       Evc_set_vhi_name(rc, vcVHI_CONTEXT, buf, sizeof("rpm-fake.so #")+l-1);
206
207       caps.ccaps =  0ull;
208       caps.cmask = ~0ull;
209       caps.bcaps = ~vc_get_insecurebcaps();
210       caps.bmask = ~0ull;
211       Evc_set_ccaps(rc, &caps);
212       
213         // context will be activated later...
214
215       xid = rc;
216       res = true;
217     }
218   }
219
220   if (xid==VC_DYNAMIC_XID)
221     *xid_str = 0;
222   else {
223     char                buf[sizeof(xid_t)*3 + 2];
224     size_t              l;
225     
226     l        = utilvserver_fmt_uint(buf, xid); buf[l] = '\0';
227     *xid_str = strdup(buf);
228   }
229  
230   Ewrite(3, &xid, sizeof xid);
231   return res;
232 }
233
234 #if 0
235 static void
236 initPwSocket()
237 {
238   char const *  sock_name = getenv("RPM_FAKE_PWSOCKET");
239   if (sock_name!=0) {
240     int flag;
241     struct sockaddr_un  addr = {
242       .sun_family = AF_UNIX,
243     };
244
245     strncpy(addr.sun_path, sock_name, sizeof(addr.sun_path)-1);
246     addr.sun_path[sizeof(addr.sun_path)-1]='\0';
247     
248     if ((pw_sock=socket(AF_UNIX, SOCK_STREAM, 0))==-1 ||
249         connect(pw_sock, (struct sockaddr *)(&addr), sizeof addr)==-1 ||
250         (flag=fcntl(pw_sock, F_GETFD))==-1 ||
251         fcntl(pw_sock, F_SETFD, flag | FD_CLOEXEC)==-1) {
252       perror(ENSC_WRAPPERS_PREFIX "error while initializing pw-socket");
253       exit(255);
254     }
255   }
256   unsetenv("RPM_FAKE_PWSOCKET");
257 }
258 #else
259 static void
260 initPwSocket()
261 {
262   char const *  resolver = getenv("RPM_FAKE_RESOLVER");
263   if (resolver==0) resolver=RESOLVER_PROG;
264   
265   if (resolver!=0 && *resolver!='\0') {
266     int                 res_sock[2];
267     int                 sync_pipe[2];
268     pid_t               pid;
269     char const *        uid=0;
270     char const *        gid=0;
271
272     uid=getenv("RPM_FAKE_RESOLVER_UID");
273     gid=getenv("RPM_FAKE_RESOLVER_GID");
274
275     if (socketpair(AF_UNIX, SOCK_STREAM, 0, res_sock)==-1 ||
276         pipe(sync_pipe)==-1 ||
277         fcntl(res_sock[0],  F_SETFD, FD_CLOEXEC)==-1 ||
278         fcntl(sync_pipe[0], F_SETFD, FD_CLOEXEC)==-1) {
279       perror(ENSC_WRAPPERS_PREFIX "failed to create/initialize resolver-socket or pipe");
280       exit(255);
281     }
282
283     pid = fork();
284     if (pid==-1) {
285       perror(ENSC_WRAPPERS_PREFIX "fork()");
286       exit(255);
287     }
288
289     if (pid==0) {
290       char const        *args[20];
291       char const        **ptr  = args;
292       char const        *env[] = { "HOME=/", "PATH=/bin:/usr/bin", 0 };
293       char const        *xid_str;
294       char              flag_str[ sizeof(flags)*3 + 2];
295       char              caps_str[ sizeof(caps)*3  + 2];
296       
297       setsid();
298       dup2(res_sock[1],  0);
299       dup2(res_sock[1],  1);
300       if (sync_pipe[1]!=3) {
301         close(3);
302         dup2(sync_pipe[1], 3);
303         close(sync_pipe[1]);
304       }
305       close(res_sock[1]);
306         /* ... *socket[0] are marked as close-on-exec ...*/
307
308       flag_str[utilvserver_fmt_uint(flag_str, flags)] = '\0';
309       caps_str[utilvserver_fmt_uint(caps_str, caps)]  = '\0';
310
311       *ptr++ = resolver;
312       *ptr++ = "-F"; *ptr++ = flag_str;
313       *ptr++ = "-C"; *ptr++ = caps_str;
314       if (root)  { *ptr++ = "-r"; *ptr++ = ".";   }
315       if (uid)   { *ptr++ = "-u"; *ptr++ = uid;   }
316       if (gid)   { *ptr++ = "-g"; *ptr++ = gid;   }
317
318       if (root) Echdir(root);
319
320       if (setupContext(ctx, &xid_str)) { *ptr++ = "-s"; }
321       else if (xid_str)                { *ptr++ = "-c"; *ptr++ = xid_str; }
322       
323       *ptr++ = 0;
324       execve(resolver, (char **)args, (char **)env);
325       perror(ENSC_WRAPPERS_PREFIX "failed to exec resolver");
326       exit(255);
327     }
328     else {
329       uint8_t           c;
330
331       close(res_sock[1]);
332       close(sync_pipe[1]);
333       pw_sock   = res_sock[0];
334       sync_sock = sync_pipe[0];
335
336       if (read(sync_sock, &ctx, sizeof ctx)!=sizeof(ctx) ||
337           read(sync_sock, &c, 1)!=1 ||
338           write(pw_sock, ".", 1)!=1 ||
339           read(pw_sock, &c,   1)!=1 ||
340           c!='.') {
341         WRITE_MSG(2, ENSC_WRAPPERS_PREFIX "failed to initialize communication with resolver\n");
342         exit(255);
343       }
344
345       if (wait4(pid, 0, WNOHANG,0)==-1) {
346         WRITE_MSG(2, ENSC_WRAPPERS_PREFIX" unexpected initialization-error of resolver\n");
347         exit(255);
348       }
349     }
350
351     unsetenv("RPM_FAKE_RESOLVER_GID");
352     unsetenv("RPM_FAKE_RESOLVER_UID");
353     unsetenv("RPM_FAKE_RESOLVER");    
354   }
355 }
356 #endif
357
358 static void
359 initEnvironment()
360 {
361   int           syscall_rev;
362   int           syscall_nr;
363   
364   if (is_initialized) return;
365
366   syscall_rev = getAndClearEnv("RPM_FAKE_S_CONTEXT_REV", 0);
367   syscall_nr  = getAndClearEnv("RPM_FAKE_S_CONTEXT_NR",  273);
368   
369 #ifdef VC_ENABLE_API_LEGACY
370   {
371     extern void vc_init_internal_legacy(int ctx_rev, int ctx_number,
372                                         int ipv4_rev, int ipv4_number);
373   
374     vc_init_internal_legacy(syscall_rev, syscall_nr, 3, 274);
375   }
376 #endif
377
378   ctx       = getAndClearEnv("RPM_FAKE_CTX",  VC_DYNAMIC_XID);
379   caps      = getAndClearEnv("RPM_FAKE_CAP",  ~0x3404040f);
380   flags     = getAndClearEnv("RPM_FAKE_FLAGS", 0);
381   root      = getenv("RPM_FAKE_CHROOT");
382   mnts      = getenv("RPM_FAKE_NAMESPACE_MOUNTS");
383   if (mnts && *mnts) mnts = strdup(mnts);
384   else               mnts = 0;
385
386   unsetenv("RPM_FAKE_CHROOT");
387   unsetenv("RPM_FAKE_NAMESPACE_MOUNTS");
388
389   
390   is_initialized = true;
391 }
392
393 static void
394 initSymbols()
395 {
396     //INIT(RTLD_NEXT, rpm_execcon);
397     //INIT(RTLD_NEXT, execv);
398   INIT(RTLD_NEXT, getgrnam);
399   INIT(RTLD_NEXT, getpwnam);
400   INIT(RTLD_NEXT, endpwent);
401   INIT(RTLD_NEXT, endgrent);
402 }
403
404 static void
405 fixPreloadEnv()
406 {
407   char                  *env = getenv("LD_PRELOAD");
408   char                  *pos;
409
410     // the const <-> non-const assignment is not an issue since the following
411     // modifying operations will not be executed in the const-case
412   env = env ? env : "";
413   pos = strstr(env, LIBNAME);
414
415   if (pos!=0) {
416     char        *end_pos = pos + sizeof(LIBNAME);
417     bool        is_end = (end_pos[-1]=='\0');
418     char        *start_pos;
419
420     end_pos[-1] = '\0';
421     start_pos   = strrchr(env, ':');
422     if (start_pos==0) start_pos = env;
423     else if (!is_end) ++start_pos;
424
425     if (is_end) *start_pos = '\0';
426     else        memmove(start_pos, end_pos, strlen(end_pos)+1);
427   }
428
429 #ifdef DEBUG
430   if (isDbgLevel(DBG_VERBOSE1|DBG_VARIABLES)) {
431     WRITE_MSG(2, "env='");
432     WRITE_STR(2, env);
433     WRITE_MSG(2, "'\n");
434   }
435 #endif
436
437   if (*env=='\0') unsetenv("LD_PRELOAD");
438 }
439
440 void
441 initRPMFake()
442 {
443   if (getenv("RPM_FAKE_VERSION")) showVersion();
444   if (getenv("RPM_FAKE_HELP"))    showHelp();
445   
446   debug_level = getAndClearEnv("RPM_FAKE_DEBUG", 0);
447
448   if (isDbgLevel(DBG_INIT)) WRITE_MSG(2, ">>>>> initRPMFake <<<<<\n");
449   
450   fixPreloadEnv();
451   initSymbols();
452   initEnvironment();
453   initPwSocket();
454
455 #if 0
456   if (isDbgLevel(DBG_VARIABLES|DBG_VERBOSE2)) {
457     
458   }
459 #endif
460 }
461
462 void
463 exitRPMFake()
464
465   if (isDbgLevel(DBG_INIT)) WRITE_MSG(2, ">>>>> exitRPMFake <<<<<\n");
466   if (pw_sock!=-1) {
467     uint8_t     c;
468     read(sync_sock, &c, 1);
469     write(pw_sock, "Q", 1);
470   }
471 }
472
473
474   //============   the worker part   ===========
475
476
477 static bool
478 doPwStringRequest(uint32_t *result, char style, char const *name)
479 {
480   uint32_t      len = strlen(name);
481   uint8_t       code;
482   uint8_t       c;
483
484     // read the token...
485   read(sync_sock, &c, 1);
486
487   write(pw_sock, &style, 1);
488   write(pw_sock, &len,   sizeof len);
489   write(pw_sock, name,   len);
490   read (pw_sock, &code,  sizeof code);
491   read (pw_sock, result, sizeof *result);
492
493   return code!=0;
494 }
495
496 struct passwd *
497 getpwnam(const char * name)
498 {
499   if (pw_sock==-1) return getpwnam_func(name);
500   else {
501     static struct passwd        res = {
502       .pw_passwd = "*",
503       .pw_gid    = -1,
504       .pw_gecos  = "",
505       .pw_dir    = "/",
506       .pw_shell  = "/bin/false"
507     };
508
509     res.pw_name = (char *)(name);
510     if (!doPwStringRequest(&res.pw_uid, 'P', name)) return 0;
511     
512     return &res;
513   }
514 }
515
516 struct group *
517 getgrnam(const char * name)
518 {
519   if (pw_sock==-1) return getgrnam_func(name);
520   else {
521     static struct group         res = {
522       .gr_passwd = "*",
523       .gr_mem    = 0
524     };
525
526     res.gr_name = (char *)(name);
527     if (!doPwStringRequest(&res.gr_gid, 'G', name)) return 0;
528
529     return &res;
530   }
531 }
532
533 void
534 endgrent()
535 {
536   if (pw_sock==-1) endgrent_func();
537 }
538
539 void
540 endpwent()
541 {
542   if (pw_sock==-1) endpwent_func();
543 }
544
545
546 static int
547 execvWorker(char const *path, char * const argv[], char * const envp[])
548 {
549   int           res = -1;
550
551   if (vc_isSupported(vcFEATURE_MIGRATE))
552     res = vc_ctx_migrate(ctx);
553   else
554 #ifdef VC_ENABLE_API_COMPAT  
555     res = vc_new_s_context(ctx,caps,flags);
556 #else
557     WRITE_MSG(2, ENSC_WRAPPERS_PREFIX "can not change context: migrate kernel feature missing and 'compat' API disabled\n");
558 #endif
559     
560   if (res!=-1)
561     res=execve(path, argv, envp);
562
563   return res;
564 }
565
566 struct ExecvParams
567 {
568     char const *        path;
569     char * const *      argv;
570     char * const *      envp;
571     char const *        mnts;
572 };
573
574 static int
575 removeNamespaceMountsChild(struct ExecvParams const *params)
576 {
577   char                  buf[strlen(params->mnts)+1], *ptr;
578
579   strcpy(buf, params->mnts);
580   ptr = strtok(buf, ":");
581   while (ptr) {
582     if (umount2(ptr, 0)==-1) {
583         // FIXME: What is the semantic for CLONE_NEWNS? Is it ok that mounts in
584         // chroots are visible only, when chroot is on /dev/root?
585         //
586         // For now, ignore any errors, but future versions should handle them.
587
588         //return -1;
589     }
590     ptr = strtok(0, ":");
591   }
592
593   return execvWorker(params->path, params->argv, params->envp);
594 }
595
596 static int
597 removeNamespaceMounts(char const *path,
598                       char * const argv[], char * const envp[])
599 {
600   if (mnts==0) return execvWorker(path, argv, envp);
601
602   {
603     int                         status;
604     pid_t                       p, pid;
605     struct ExecvParams          params;
606
607     params.path = path;
608     params.argv = argv;
609     params.envp = envp;
610     params.mnts = mnts;
611
612       // the rpmlib signal-handler is still active; use the default one to
613       // make wait4() working...
614     signal(SIGCHLD, SIG_DFL);
615
616 #ifdef NDEBUG
617     pid = sys_clone(CLONE_NEWNS|SIGCHLD|CLONE_VFORK, 0);
618 #else
619     pid = sys_clone(CLONE_NEWNS|SIGCHLD, 0);
620 #endif
621
622     switch (pid) {
623       case -1   :  return -1;
624       case 0    :  _exit(removeNamespaceMountsChild(&params));
625       default   :  break;
626     }
627         
628     while ((p=wait4(pid, &status, 0,0))==-1 &&
629            (errno==EINTR || errno==EAGAIN)) ;
630
631     if (p==-1)   return -1;
632
633     if (WIFEXITED(status))   _exit(WEXITSTATUS(status));
634     if (WIFSIGNALED(status)) kill(getpid(), WTERMSIG(status));
635
636     return -1;
637   }
638 }
639
640
641 int
642 execv(char const *path, char * const argv[])
643 {
644   extern char **environ;
645
646   return removeNamespaceMounts(path, argv, environ);
647 }
648
649 int
650 rpm_execcon(unsigned int UNUSED verified,
651             const char *filename,
652             char *const argv[], char *const envp[])
653 {
654   return removeNamespaceMounts(filename, argv, envp);
655 }