X-Git-Url: http://git.linux-vserver.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=util-vserver%2Fsrc%2Frpm-fake.c;h=b3b641a9436c94f88e2bed380ace2d5c3c19a1cd;hb=5341b9ca6659aa2f67eac0d58148ecaa1d2f4551;hp=ffd26ef9913cf581fb356796a43616b3eff04161;hpb=b562f3d09686bb1686646c949b6f92c94eb2d64c;p=util-vserver.git diff --git a/util-vserver/src/rpm-fake.c b/util-vserver/src/rpm-fake.c index ffd26ef..b3b641a 100644 --- a/util-vserver/src/rpm-fake.c +++ b/util-vserver/src/rpm-fake.c @@ -20,15 +20,23 @@ # include #endif +#include "pathconfig.h" +#include "util.h" + +#include +#include +#include + +#include #include #include #include #include #include +#include #include #include #include -#include #include #include #include @@ -37,104 +45,138 @@ #include #include #include +#include +#include +#include +#include -#define LIBNAME "rpm-fake.so" -#define PLATFORM_FILE "/etc/rpm/platform" -#define INIT(FILE,FUNC) FUNC##_func = dlsym(FILE, #FUNC) -#define DECLARE(FUNC) static __typeof__(FUNC) * FUNC##_func = 0 + // from selinux.h + // FIXME: add configure autodetection and include directly +int rpm_execcon(unsigned int verified, + const char *filename, + char *const argv[], char *const envp[]); -static bool is_initialized = false; -DECLARE(execv); - //DECLARE(open); -static int def_NR_new_s_context = 259; -#undef __NR_new_s_context -static int __NR_new_s_context_rev0; -static int __NR_new_s_context_rev1; +#define ENSC_WRAPPERS_PREFIX "rpm-fake.so: " +#define ENSC_WRAPPERS_VSERVER 1 +#define ENSC_WRAPPERS_UNISTD 1 +#include -static int new_s_context_rev0(int, int, int); -static int new_s_context_rev1(int, int *, int, int); +#undef _POSIX_SOURCE +#include "capability-compat.h" -_syscall3(int, new_s_context_rev0, int, newctx, int, remove_cap, int, flags) -_syscall4(int, new_s_context_rev1, int, nbctx, int *, ctxs, int, remove_cap, int, flags) +#ifndef CLONE_NEWNS +# define CLONE_NEWNS 0x00020000 +#endif -static int -new_s_context(int rev, - int nbctx, int *ctxs, int remove_cap, int flags) -{ - int ret; - - switch (rev) { - case 0 : - { - int ctx; - - switch (nbctx) { - case 0 : ctx = -1; break; - case 1 : ctx = ctxs[0]; break; - default : errno = EINVAL; return -1; - } +#define LIBNAME "rpm-fake.so" +#define PLATFORM_FILE "/etc/rpm/platform" - ret = new_s_context_rev0(ctx, remove_cap, flags); +#define INIT(FILE,FUNC) FUNC##_func = ((__typeof__(FUNC) *) (xdlsym(FILE, #FUNC))) +#define DECLARE(FUNC) static __typeof__(FUNC) * FUNC##_func = 0 - break; - } +#define DEBUG 1 - case 1 : - ret = new_s_context_rev1(nbctx, ctxs, remove_cap, flags); - break; +#define DBG_INIT 0x0001 +#define DBG_VARIABLES 0x0002 +#define DBG_RESOLVER 0x0004 +#define DBG_EXECV 0x0008 +#define DBG_ENV 0x0010 +#define DBG_VERBOSE0 0x8000 +#define DBG_VERBOSE1 (0x4000 | DBG_VERBOSE0) +#define DBG_VERBOSE2 (0x2000 | DBG_VERBOSE1) - default : - errno = EINVAL; return -1; - } +int wrapper_exit_code = 255; - return ret; -} +static xid_t ctx = VC_NOCTX; +static uint32_t caps = ~0; +static int flags = 0; +static char const * mnts = 0; +static char const * root = 0; +static int pw_sock = -1; +static int sync_sock = -1; +static unsigned int debug_level = 0; -static void -initVserverSyscalls(int s_context_NR) -{ - int nr = (s_context_NR!=-1) ? s_context_NR : def_NR_new_s_context; +static bool is_initialized = false; - __NR_new_s_context_rev0 = __NR_new_s_context_rev1 = nr; -} + //DECLARE(rpm_execcon); + //DECLARE(execv); +DECLARE(getpwnam); +DECLARE(getgrnam); +DECLARE(endpwent); +DECLARE(endgrent); -static int -getAndClearEnv(char const *key, int dflt) +static void initRPMFake() __attribute__((__constructor__)); +static void exitRPMFake() __attribute__((__destructor__)); + +static inline bool +isDbgLevel(unsigned int level) { - char *env = getenv(key); - int res; + return ((debug_level&level)==level); +} - if (env==0 || env[0]=='\0') res = dflt; - else { - res = atoi(env); - unsetenv(key); +static void * +xdlsym(void *handle, const char *symbol) +{ + void *res = dlsym(handle, symbol); + if (res==0) { + char const *error = dlerror(); + write(2, symbol, strlen(symbol)); + write(2, ": ", 2); + write(2, error, strlen(error)); + write(2, "\n", 2); + + _exit(255); } return res; } static void -initLib() +showHelp() { - if (is_initialized) return; - - initVserverSyscalls(getAndClearEnv("RPM_FAKE_S_CONTEXT_NR", -1)); - INIT(RTLD_NEXT, execv); - //INIT(RTLD_NEXT, open); + WRITE_MSG(1, + "Usage: LD_PRELOAD=" LIBNAME " *\n\n" + LIBNAME " unterstands the following environment variables:\n" + " $RPM_FAKE_RESOLVER ... program which does the NSS resolving (defaults\n" + " to " RESOLVER_PROG ")\n" + " $RPM_FAKE_RESOLVER_UID ... uid of the resolver program\n" + " $RPM_FAKE_RESOLVER_GID ... gid of the resolver program\n" + " $RPM_FAKE_CTX ... vserver context which shall be used for resolver\n" + " and scriptlets\n" + " $RPM_FAKE_CAP ... linux capability remove-mask for the context\n" + " $RPM_FAKE_FLAGS ... vserver flags of the context\n" + " $RPM_FAKE_CHROOT ... directory of the chroot environment\n" + " $RPM_FAKE_NAMESPACE_MOUNTS\n" + " ... colon separated list of directories which will\n" + " umounted before scriptlet execution\n\n" + " $RPM_FAKE_HELP ... shows this message\n" + " $RPM_FAKE_VERSION ... shows the version of this program\n\n" + " $RPM_FAKE_DEBUG ... sets the debuglevel bitmask\n\n" + "Please report bugs to " PACKAGE_BUGREPORT "\n"); + exit(0); +} - is_initialized = true; +static void +showVersion() +{ + WRITE_MSG(1, + LIBNAME " " VERSION " -- wrapper around rpm\n" + "This program is part of " PACKAGE_STRING "\n\n" + "Copyright (C) 2003 Enrico Scholz\n" + VERSION_COPYRIGHT_DISCLAIMER); + exit(0); } static void -fixPreloadEnv() +unsetPreloadEnv() { char *env = getenv("LD_PRELOAD"); char *pos; - // the const <-> non-const assignment is not an issue since the following modifying operations - // will not be executed in the const-case + // the const <-> non-const assignment is not an issue since the following + // modifying operations will not be executed in the const-case env = env ? env : ""; pos = strstr(env, LIBNAME); @@ -142,7 +184,7 @@ fixPreloadEnv() char *end_pos = pos + sizeof(LIBNAME); bool is_end = (end_pos[-1]=='\0'); char *start_pos; - + end_pos[-1] = '\0'; start_pos = strrchr(env, ':'); if (start_pos==0) start_pos = env; @@ -153,27 +195,420 @@ fixPreloadEnv() } #ifdef DEBUG - printf("env='%s'\n", env); + if (isDbgLevel(DBG_VERBOSE1|DBG_VARIABLES)) { + WRITE_MSG(2, "env='"); + WRITE_STR(2, env); + WRITE_MSG(2, "'\n"); + } #endif if (*env=='\0') unsetenv("LD_PRELOAD"); } +static void +clearEnv() +{ + if (isDbgLevel(DBG_ENV)) WRITE_MSG(2, "clearEnv()\n"); + + unsetenv("RPM_FAKE_S_CONTEXT_REV"); + unsetenv("RPM_FAKE_S_CONTEXT_NR"); + unsetenv("RPM_FAKE_CTX"); + unsetenv("RPM_FAKE_FLAGS"); + unsetenv("RPM_FAKE_CHROOT"); + unsetenv("RPM_FAKE_NAMESPACE_MOUNTS"); + + unsetenv("RPM_FAKE_RESOLVER_GID"); + unsetenv("RPM_FAKE_RESOLVER_UID"); + unsetenv("RPM_FAKE_RESOLVER"); + unsetenv("RPM_FAKE_PWSOCKET"); + + unsetenv("RPM_FAKE_DEBUG"); + + unsetPreloadEnv(); +} + static int -execvWorker(char const *path, char * const argv[]) +getDefaultEnv(char const *key, int dflt) { + char *env = getenv(key); int res; - int ctx; - - printf("+execvWorker\n"); - ctx = getAndClearEnv("RPM_FAKE_CTX", -1); - if ( (res=new_s_context(getAndClearEnv("RPM_FAKE_S_CONTEXT_REV", 0), - ctx==-1 ? 0 : 1, - &ctx, - getAndClearEnv("RPM_FAKE_CAP", ~0x3404040f), - getAndClearEnv("RPM_FAKE_FLAGS", 0)))!=-1 && - (res=execv_func(path, argv)!=-1) ) {} - printf("-execvWorker -> res=%u, err=%s\n",res,strerror(errno)); + + if (env==0 || env[0]=='\0') res = dflt; + else res = atoi(env); + + return res; +} + + /// \returns true iff we are in ctx after leaving this function +static bool +setupContext(xid_t xid, char const **xid_str) +{ + bool res = false; + + if (vc_isSupported(vcFEATURE_MIGRATE)) { + xid_t rc=VC_NOCTX; + + if ((xid==VC_DYNAMIC_XID || !vc_is_dynamic_xid(xid)) && + (rc=vc_ctx_create(xid))==VC_NOCTX && + errno!=EEXIST) { + perror(ENSC_WRAPPERS_PREFIX "vc_create_context()"); + exit(255); + } + + if (rc!=VC_NOCTX) { + char buf[sizeof(xid_t)*3 + 128]; + size_t l; + struct vc_ctx_caps caps; + + strcpy(buf, "rpm-fake.so #"); + l = utilvserver_fmt_uint(buf+sizeof("rpm-fake.so #")-1, getppid()); + Evc_set_vhi_name(rc, vcVHI_CONTEXT, buf, sizeof("rpm-fake.so #")+l-1); + + caps.ccaps = 0ull; + caps.cmask = ~0ull; + caps.bcaps = ~vc_get_insecurebcaps(); + caps.bmask = ~0ull; + Evc_set_ccaps(rc, &caps); + + // context will be activated later... + + xid = rc; + res = true; + } + } + + if (xid==VC_DYNAMIC_XID) + *xid_str = 0; + else { + char buf[sizeof(xid_t)*3 + 2]; + size_t l; + + l = utilvserver_fmt_uint(buf, xid); buf[l] = '\0'; + *xid_str = strdup(buf); + } + + Ewrite(3, &xid, sizeof xid); + return res; +} + +#if 0 +static void +initPwSocket() +{ + char const * sock_name = getenv("RPM_FAKE_PWSOCKET"); + if (sock_name!=0) { + int flag; + struct sockaddr_un addr = { + .sun_family = AF_UNIX, + }; + + strncpy(addr.sun_path, sock_name, sizeof(addr.sun_path)-1); + addr.sun_path[sizeof(addr.sun_path)-1]='\0'; + + if ((pw_sock=socket(AF_UNIX, SOCK_STREAM, 0))==-1 || + connect(pw_sock, (struct sockaddr *)(&addr), sizeof addr)==-1 || + (flag=fcntl(pw_sock, F_GETFD))==-1 || + fcntl(pw_sock, F_SETFD, flag | FD_CLOEXEC)==-1) { + perror(ENSC_WRAPPERS_PREFIX "error while initializing pw-socket"); + exit(255); + } + } +} +#else +static void +initPwSocket() +{ + char const * resolver = getenv("RPM_FAKE_RESOLVER"); + if (resolver==0) resolver=RESOLVER_PROG; + + if (resolver!=0 && *resolver!='\0') { + int res_sock[2]; + int sync_pipe[2]; + pid_t pid; + char const * uid=0; + char const * gid=0; + + uid=getenv("RPM_FAKE_RESOLVER_UID"); + gid=getenv("RPM_FAKE_RESOLVER_GID"); + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, res_sock)==-1 || + pipe(sync_pipe)==-1 || + fcntl(res_sock[0], F_SETFD, FD_CLOEXEC)==-1 || + fcntl(sync_pipe[0], F_SETFD, FD_CLOEXEC)==-1) { + perror(ENSC_WRAPPERS_PREFIX "failed to create/initialize resolver-socket or pipe"); + exit(255); + } + + pid = fork(); + if (pid==-1) { + perror(ENSC_WRAPPERS_PREFIX "fork()"); + exit(255); + } + + if (pid==0) { + char const *args[20]; + char const **ptr = args; + char const *env[] = { "HOME=/", "PATH=/bin:/usr/bin", 0 }; + char const *xid_str; + char flag_str[ sizeof(flags)*3 + 2]; + char caps_str[ sizeof(caps)*3 + 2]; + + clearEnv(); + + setsid(); + dup2(res_sock[1], 0); + dup2(res_sock[1], 1); + if (sync_pipe[1]!=3) { + close(3); + dup2(sync_pipe[1], 3); + close(sync_pipe[1]); + } + close(res_sock[1]); + /* ... *socket[0] are marked as close-on-exec ...*/ + + flag_str[utilvserver_fmt_uint(flag_str, flags)] = '\0'; + caps_str[utilvserver_fmt_uint(caps_str, caps)] = '\0'; + + *ptr++ = resolver; + *ptr++ = "-F"; *ptr++ = flag_str; + *ptr++ = "-C"; *ptr++ = caps_str; + if (root) { *ptr++ = "-r"; *ptr++ = "."; } + if (uid) { *ptr++ = "-u"; *ptr++ = uid; } + if (gid) { *ptr++ = "-g"; *ptr++ = gid; } + + if (root) Echdir(root); + + if (setupContext(ctx, &xid_str)) { *ptr++ = "-s"; } + else if (xid_str) { *ptr++ = "-c"; *ptr++ = xid_str; } + + *ptr++ = 0; + execve(resolver, (char **)args, (char **)env); + perror(ENSC_WRAPPERS_PREFIX "failed to exec resolver"); + exit(255); + } + else { + uint8_t c; + + close(res_sock[1]); + close(sync_pipe[1]); + pw_sock = res_sock[0]; + sync_sock = sync_pipe[0]; + + if (read(sync_sock, &ctx, sizeof ctx)!=sizeof(ctx) || + read(sync_sock, &c, 1)!=1 || + write(pw_sock, ".", 1)!=1 || + read(pw_sock, &c, 1)!=1 || + c!='.') { + WRITE_MSG(2, ENSC_WRAPPERS_PREFIX "failed to initialize communication with resolver\n"); + exit(255); + } + + if (wait4(pid, 0, WNOHANG,0)==-1) { + WRITE_MSG(2, ENSC_WRAPPERS_PREFIX" unexpected initialization-error of resolver\n"); + exit(255); + } + } + } +} +#endif + +static void +reduceCapabilities() +{ + struct __user_cap_header_struct header; + struct __user_cap_data_struct user; + + header.version = _LINUX_CAPABILITY_VERSION; + header.pid = 0; + + if (capget(&header, &user)==-1) { + perror("capget()"); + exit(wrapper_exit_code); + } + + user.effective &= ~(1<>>>> initRPMFake <<<<<\n"); + + reduceCapabilities(); + initSymbols(); + initEnvironment(); + initPwSocket(); + +#if 0 + if (isDbgLevel(DBG_VARIABLES|DBG_VERBOSE2)) { + + } +#endif +} + +void +exitRPMFake() +{ + if (isDbgLevel(DBG_INIT)) WRITE_MSG(2, ">>>>> exitRPMFake <<<<<\n"); + if (pw_sock!=-1) { + uint8_t c; + read(sync_sock, &c, 1); + write(pw_sock, "Q", 1); + } +} + + + //============ the worker part =========== + + +static bool +doPwStringRequest(uint32_t *result, char style, char const *name) +{ + uint32_t len = strlen(name); + uint8_t code; + uint8_t c; + + // read the token... + read(sync_sock, &c, 1); + + write(pw_sock, &style, 1); + write(pw_sock, &len, sizeof len); + write(pw_sock, name, len); + read (pw_sock, &code, sizeof code); + read (pw_sock, result, sizeof *result); + + return code!=0; +} + +struct passwd * +getpwnam(const char * name) +{ + if (pw_sock==-1) return getpwnam_func(name); + else { + static struct passwd res = { + .pw_passwd = "*", + .pw_gid = -1, + .pw_gecos = "", + .pw_dir = "/", + .pw_shell = "/bin/false" + }; + + res.pw_name = (char *)(name); + if (!doPwStringRequest(&res.pw_uid, 'P', name)) return 0; + + return &res; + } +} + +struct group * +getgrnam(const char * name) +{ + if (pw_sock==-1) return getgrnam_func(name); + else { + static struct group res = { + .gr_passwd = "*", + .gr_mem = 0 + }; + + res.gr_name = (char *)(name); + if (!doPwStringRequest(&res.gr_gid, 'G', name)) return 0; + + return &res; + } +} + +void +endgrent() +{ + if (pw_sock==-1) endgrent_func(); +} + +void +endpwent() +{ + if (pw_sock==-1) endpwent_func(); +} + + +static int +execvWorker(char const *path, char * const argv[], char * const envp[]) +{ + int res = -1; + + if (vc_isSupported(vcFEATURE_MIGRATE)) + res = vc_ctx_migrate(ctx); + else { +#ifdef VC_ENABLE_API_COMPAT + res = vc_new_s_context(ctx,caps,flags); +#else + WRITE_MSG(2, ENSC_WRAPPERS_PREFIX "can not change context: migrate kernel feature missing and 'compat' API disabled\n"); +#endif + } + + clearEnv(); + + if (res!=-1) + res=execve(path, argv, envp); return res; } @@ -182,77 +617,101 @@ struct ExecvParams { char const * path; char * const * argv; + char * const * envp; char const * mnts; }; static int -removeNamespaceMountsChild(void *params_v) +removeNamespaceMountsChild(struct ExecvParams const *params) { - struct ExecvParams * params = params_v; char buf[strlen(params->mnts)+1], *ptr; strcpy(buf, params->mnts); ptr = strtok(buf, ":"); while (ptr) { - system("cat /proc/mounts"); - system("pstree -h"); - system("ls -l /proc/self/ /proc/self/fd/ /var/lib /var/lib/rpm/"); - if (umount2(ptr, 0)==-1) { dprintf(2, "ptr='%s' -> ", ptr); perror("umount2()"); return -1; } + if (umount2(ptr, 0)==-1) { + // FIXME: What is the semantic for CLONE_NEWNS? Is it ok that mounts in + // chroots are visible only, when chroot is on /dev/root? + // + // For now, ignore any errors, but future versions should handle them. + + //return -1; + } ptr = strtok(0, ":"); } - unsetenv("RPM_FAKE_NAMESPACE_MOUNTS"); - return execvWorker(params->path, params->argv); + return execvWorker(params->path, params->argv, params->envp); } static int -removeNamespaceMounts(char const *path, char * const argv[]) +removeNamespaceMounts(char const *path, + char * const argv[], char * const envp[]) { - char const * mnts = getenv("RPM_FAKE_NAMESPACE_MOUNTS"); - - printf("mnts='%s'\n", mnts); - if (mnts==0) return execvWorker(path, argv); + if (mnts==0) return execvWorker(path, argv, envp); { - printf("l=%u\n", __LINE__); - char buf[512 + 2*strlen(mnts)]; - int status; - pid_t p; - struct ExecvParams params = { - .path = path, - .argv = argv, - .mnts = mnts, - }; - - printf("l=%u\n", __LINE__); + int status; + pid_t p, pid; + struct ExecvParams params; + + params.path = path; + params.argv = argv; + params.envp = envp; + params.mnts = mnts; + + // the rpmlib signal-handler is still active; use the default one to + // make wait4() working... + signal(SIGCHLD, SIG_DFL); + +#ifdef NDEBUG + pid = sys_clone(CLONE_NEWNS|SIGCHLD|CLONE_VFORK, 0); +#else + pid = sys_clone(CLONE_NEWNS|SIGCHLD, 0); +#endif - - system("mount -t proc none /proc"); - system("cat /proc/mounts"); - //system("pstree -h"); - system("ls -l /proc/self/ /proc/self/fd/ /var/lib /var/lib/rpm/"); - pid_t pid = clone(removeNamespaceMountsChild, buf+sizeof(buf)/2, - CLONE_NEWNS|SIGCHLD|CLONE_VFORK, ¶ms); - - printf("l=%u\n", __LINE__); - if (pid==-1) return -1; - while ((p=waitpid(pid, &status, 0))==-1 && + switch (pid) { + case -1 : return -1; + case 0 : _exit(removeNamespaceMountsChild(¶ms)); + default : break; + } + + while ((p=wait4(pid, &status, 0,0))==-1 && (errno==EINTR || errno==EAGAIN)) ; - if (p==-1) return -1; - printf("removeNamespaceMountsChild() -> status=%u\n", status); + if (p==-1) return -1; - if (WIFEXITED(status)) exit(WEXITSTATUS(status)); + if (WIFEXITED(status)) _exit(WEXITSTATUS(status)); if (WIFSIGNALED(status)) kill(getpid(), WTERMSIG(status)); return -1; } } + +int +execv(char const *path, char * const argv[]) +{ + extern char **environ; + + if (isDbgLevel(DBG_EXECV)) { + WRITE_MSG(2, "execv('"); + WRITE_STR(2, path); + WRITE_MSG(2, "', ...)\n"); + } + + return removeNamespaceMounts(path, argv, environ); +} + int -execv(char const *path, char * const argv[]) __THROW +rpm_execcon(unsigned int UNUSED verified, + const char *filename, + char *const argv[], char *const envp[]) { - initLib(); - fixPreloadEnv(); - return removeNamespaceMounts(path, argv); + if (isDbgLevel(DBG_EXECV)) { + WRITE_MSG(2, "rpm_execcon(..., '"); + WRITE_STR(2, filename); + WRITE_MSG(2, "', ...)\n"); + } + + return removeNamespaceMounts(filename, argv, envp); }