selinux/policycoreutils/sandbox/seunshare.c
Steve Lawrence 582fd00c7b Author: Steve Lawrence
Email: slawrence@tresys.com
Subject: Updated sandbox patch.
Date: Mon, 07 Jun 2010 17:53:41 -0400

On Thu, 2010-05-27 at 08:57 -0400, Daniel J Walsh wrote:
> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
>
> On 05/26/2010 04:06 PM, Steve Lawrence wrote:
> > On Wed, 2010-05-19 at 15:59 -0400, Daniel J Walsh wrote:
> > Fixed patch that handles Spaces in homedir.
>
> > The following patch makes a few updates the the sandbox patch, though I
> > have a question:
>
> > Is the sandbox.init script needed anymore? It looks like seunshare was
> > changed to now bind mount and make private the necessary directories.
> > The only thing that seems missing is making root rshared. Also, if the
> > init script is obsolete, do the mounts also need the MS_REC flag for
> > recursive bind/private like they are mounted in the init script? e.g.
>
> The init script is needed for the xguest package/more specifically
> pam_namespace, but also needed for
> mount --make-rshared /
>
> Whether the init script belongs in policycoreutils is questionable though.
>
>
> > mount(dst, dst, NULL, (MS_BIND | MS_REC), NULL)
> > mount(dst, dst, NULL, (MS_PRIVATE | MS_REC), NULL)
>
> We probably should add these.  Although it is not likely.
>
> > Changes the following patch makes:
>
> > sandbox.py
> > - Removes unused 'import commands'
> > - Fixes the chcon function, and replaces the deprecated os.path.walk
> >   with os.walk. I think this way is a bit easier to read too.
>
> I think chcon should be added to libselinux python bindings and then
> leave the recursive flag.  (restorecon is currently in python bindings._
>
> > - Removes the 'yum install seunshare' message. This tool is not specific
> >   to RPM based distros.
>
> People are using seunshare without X now that I have added the -M flag.
>  So I will move it from the -gui package to the base package with
> sandbox and then this should not be necessary.
> > - Remove try/except around -I include to be consistent with the -i
> >   option. If we can't include a file, then this should bail, no matter
> >   if it's being included via -i or -I.
>
> Ok, I was thinking you could list a whole bunch of files in the -I case
> and if one does not exist, allow it to continue.  But I don't really care.
> > - Fix homedir/tmpdir typo in chcon call
>
> > sandbox.init (maybe obsoleted?)
> > - Fix restart so it stops and starts
> > - unmount the bind mounts when stopped
> I doubt this will work.  Two many locks in /tmp /home
> > - Abort with failure if any mounts fail
>
> > seunshare.c
> > - Define the mount flag MS_PRIVATE if it isn't already. The flag is only
> >   defined in the latest glibc but has been in the kernel since 2005.
> > - Simplify an if-statment. Also, I'm not sure the purpose of the
> >   strncmmp in that conditional, so maybe I've oversimplified.
> This is wrong.  The problem comes about when you mount within the same
> directory.
>
> seunshare -t /home/dwalsh/sanbox/tmp -h /home/dwalsh/sandbox/home   ...
>
> seunshare -t /tmp/sandbox/tmp -h /tmp/sandbox/home
>
> If you do not have the check one of the above will fail.
>
> In the first example if Homedir is mounted first,
> /home/dwalsh/sanbox/tmp will no longer exist when seunshare attempts to
> mount it on /tmp.
>
> Similarly, if /tmp is mounted first in the second example.
> /tmp/sandbox/home will no longer exist.
>
> You have to check to make sure one of the directories is not included in
> the other.
>
> It seems
> >   like maybe an error should be thrown if tmpdir_s == pw_dir or
> >   homedir_s == "/tmp", but maybe I'm missing something.
>
> See above.
>
> I was blowing up because I use
>
> ~/sandbox/tmp and ~/sandbox/home for my mountpoints.

<snip>

Below is an updated patch that makes a few changes the the latest
Sandbox Patch [1]. This requires the chcon patch [2].

Changes this patch makes:

sandbox.py
- Remove unused 'import commands'
- Uses new chcon method in libselinux [2]
- Removes the 'yum install seunshare' message
- Converts an IOError to a string for printing a warning if a file
  listed in -I does not exist

sandbox.init
- Print the standard Starting/Stoping messages with the appropriate
  OK/FAIL
- Abort with failure if any mounts fail

seunshare.c
- Add the MS_REC flag during mounts to perform recursive mounts
- Define the mount flags MS_PRIVATE and MS_REC if they aren't already.
  The flags are only defined in the latest glibc but have been in the
  kernel since 2005.
- Calls realpath(3) on tmpdir_s and homedir_s. If relative paths are
  used, it wouldn't correctly detect that tmpdir is inside homedir and
  change the mount order. This fixes that.

[1] http://marc.info/?l=selinux&m=127429948731841&w=2
[2] http://marc.info/?l=selinux&m=127594712200878&w=2

Signed-off-by: Chad Sellers <csellers@tresys.com>
2010-06-10 16:37:59 -04:00

322 lines
7.1 KiB
C

#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <syslog.h>
#include <sys/mount.h>
#include <pwd.h>
#define _GNU_SOURCE
#include <sched.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <cap-ng.h>
#include <getopt.h> /* for getopt_long() form of getopt() */
#include <limits.h>
#include <stdlib.h>
#include <errno.h>
#include <selinux/selinux.h>
#include <selinux/context.h> /* for context-mangling functions */
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#ifdef USE_NLS
#include <locale.h> /* for setlocale() */
#include <libintl.h> /* for gettext() */
#define _(msgid) gettext (msgid)
#else
#define _(msgid) (msgid)
#endif
#ifndef MS_REC
#define MS_REC 1<<14
#endif
#ifndef MS_PRIVATE
#define MS_PRIVATE 1<<18
#endif
/**
* This function will drop all capabilities
* Returns zero on success, non-zero otherwise
*/
static int drop_capabilities(uid_t uid)
{
capng_clear(CAPNG_SELECT_BOTH);
if (capng_lock() < 0)
return -1;
/* Change uid */
if (setresuid(uid, uid, uid)) {
fprintf(stderr, _("Error changing uid, aborting.\n"));
return -1;
}
return capng_apply(CAPNG_SELECT_BOTH);
}
#define DEFAULT_PATH "/usr/bin:/bin"
static int verbose = 0;
/**
* Take care of any signal setup
*/
static int set_signal_handles(void)
{
sigset_t empty;
/* Empty the signal mask in case someone is blocking a signal */
if (sigemptyset(&empty)) {
fprintf(stderr, "Unable to obtain empty signal set\n");
return -1;
}
(void)sigprocmask(SIG_SETMASK, &empty, NULL);
/* Terminate on SIGHUP. */
if (signal(SIGHUP, SIG_DFL) == SIG_ERR) {
perror("Unable to set SIGHUP handler");
return -1;
}
return 0;
}
/**
* This function makes sure the mounted directory is owned by the user executing
* seunshare.
* If so, it returns 0. If it can not figure this out or they are different, it returns -1.
*/
static int verify_mount(const char *mntdir, struct passwd *pwd) {
struct stat sb;
if (stat(mntdir, &sb) == -1) {
fprintf(stderr, _("Invalid mount point %s: %s\n"), mntdir, strerror(errno));
return -1;
}
if (sb.st_uid != pwd->pw_uid) {
errno = EPERM;
syslog(LOG_AUTHPRIV | LOG_ALERT, "%s attempted to mount an invalid directory, %s", pwd->pw_name, mntdir);
perror(_("Invalid mount point, reporting to administrator"));
return -1;
}
return 0;
}
/**
* This function checks to see if the shell is known in /etc/shells.
* If so, it returns 0. On error or illegal shell, it returns -1.
*/
static int verify_shell(const char *shell_name)
{
int rc = -1;
const char *buf;
if (!(shell_name && shell_name[0]))
return rc;
while ((buf = getusershell()) != NULL) {
/* ignore comments */
if (*buf == '#')
continue;
/* check the shell skipping newline char */
if (!strcmp(shell_name, buf)) {
rc = 1;
break;
}
}
endusershell();
return rc;
}
static int seunshare_mount(const char *src, const char *dst, struct passwd *pwd) {
if (verbose)
printf("Mount %s on %s\n", src, dst);
if (mount(dst, dst, NULL, MS_BIND | MS_REC, NULL) < 0) {
fprintf(stderr, _("Failed to mount %s on %s: %s\n"), dst, dst, strerror(errno));
return -1;
}
if (mount(dst, dst, NULL, MS_PRIVATE | MS_REC, NULL) < 0) {
fprintf(stderr, _("Failed to make %s private: %s\n"), dst, strerror(errno));
return -1;
}
if (mount(src, dst, NULL, MS_BIND | MS_REC, NULL) < 0) {
fprintf(stderr, _("Failed to mount %s on %s: %s\n"), src, dst, strerror(errno));
return -1;
}
if (verify_mount(dst, pwd) < 0)
return -1;
}
#define USAGE_STRING _("USAGE: seunshare [ -v ] [ -t tmpdir ] [ -h homedir ] -- CONTEXT executable [args] ")
int main(int argc, char **argv) {
int rc;
int status = -1;
security_context_t scontext;
int flag_index; /* flag index in argv[] */
int clflag; /* holds codes for command line flags */
char *tmpdir_s = NULL; /* tmpdir spec'd by user in argv[] */
char *homedir_s = NULL; /* homedir spec'd by user in argv[] */
const struct option long_options[] = {
{"homedir", 1, 0, 'h'},
{"tmpdir", 1, 0, 't'},
{"verbose", 1, 0, 'v'},
{NULL, 0, 0, 0}
};
uid_t uid = getuid();
if (!uid) {
fprintf(stderr, _("Must not be root"));
return -1;
}
struct passwd *pwd=getpwuid(uid);
if (!pwd) {
perror(_("getpwduid failed"));
return -1;
}
if (verify_shell(pwd->pw_shell) < 0) {
fprintf(stderr, _("Error! Shell is not valid.\n"));
return -1;
}
while (1) {
clflag = getopt_long(argc, argv, "h:t:", long_options,
&flag_index);
if (clflag == -1)
break;
switch (clflag) {
case 't':
if (!(tmpdir_s = realpath(optarg, NULL))) {
fprintf(stderr, _("Invalid mount point %s: %s\n"), optarg, strerror(errno));
return -1;
}
if (verify_mount(tmpdir_s, pwd) < 0) return -1;
break;
case 'h':
if (!(homedir_s = realpath(optarg, NULL))) {
fprintf(stderr, _("Invalid mount point %s: %s\n"), optarg, strerror(errno));
return -1;
}
if (verify_mount(homedir_s, pwd) < 0) return -1;
if (verify_mount(pwd->pw_dir, pwd) < 0) return -1;
break;
case 'v':
verbose = 1;
break;
default:
fprintf(stderr, "%s\n", USAGE_STRING);
return -1;
}
}
if (! homedir_s && ! tmpdir_s) {
fprintf(stderr, _("Error: tmpdir and/or homedir required \n"),
"%s\n", USAGE_STRING);
return -1;
}
if (argc - optind < 2) {
fprintf(stderr, _("Error: context and executable required \n"),
"%s\n", USAGE_STRING);
return -1;
}
scontext = argv[optind++];
if (set_signal_handles())
return -1;
if (unshare(CLONE_NEWNS) < 0) {
perror(_("Failed to unshare"));
return -1;
}
if (homedir_s && tmpdir_s && (strncmp(pwd->pw_dir, tmpdir_s, strlen(pwd->pw_dir)) == 0)) {
if (seunshare_mount(tmpdir_s, "/tmp", pwd) < 0)
return -1;
if (seunshare_mount(homedir_s, pwd->pw_dir, pwd) < 0)
return -1;
} else {
if (homedir_s && seunshare_mount(homedir_s, pwd->pw_dir, pwd) < 0)
return -1;
if (tmpdir_s && seunshare_mount(tmpdir_s, "/tmp", pwd) < 0)
return -1;
}
if (drop_capabilities(uid)) {
perror(_("Failed to drop all capabilities"));
return -1;
}
int child = fork();
if (child == -1) {
perror(_("Unable to fork"));
return -1;
}
if (!child) {
char *display=NULL;
/* Construct a new environment */
char *d = getenv("DISPLAY");
if (d) {
display = strdup(d);
if (!display) {
perror(_("Out of memory"));
exit(-1);
}
}
if ((rc = clearenv())) {
perror(_("Unable to clear environment"));
free(display);
exit(-1);
}
if (setexeccon(scontext)) {
fprintf(stderr, _("Could not set exec context to %s.\n"),
scontext);
free(display);
exit(-1);
}
if (display)
rc |= setenv("DISPLAY", display, 1);
rc |= setenv("HOME", pwd->pw_dir, 1);
rc |= setenv("SHELL", pwd->pw_shell, 1);
rc |= setenv("USER", pwd->pw_name, 1);
rc |= setenv("LOGNAME", pwd->pw_name, 1);
rc |= setenv("PATH", DEFAULT_PATH, 1);
if (chdir(pwd->pw_dir)) {
perror(_("Failed to change dir to homedir"));
exit(-1);
}
setsid();
execv(argv[optind], argv + optind);
free(display);
perror("execv");
exit(-1);
} else {
waitpid(child, &status, 0);
}
free(tmpdir_s);
free(homedir_s);
return status;
}