2011-08-16 00:10:14 +00:00
|
|
|
/*
|
|
|
|
* Authors: Dan Walsh <dwalsh@redhat.com>
|
|
|
|
* Authors: Thomas Liu <tliu@fedoraproject.org>
|
|
|
|
*/
|
|
|
|
|
2011-08-02 17:58:07 +00:00
|
|
|
#define _GNU_SOURCE
|
2010-06-10 20:35:55 +00:00
|
|
|
#include <signal.h>
|
2011-08-15 23:58:08 +00:00
|
|
|
#include <sys/fsuid.h>
|
|
|
|
#include <sys/stat.h>
|
2010-06-10 20:35:55 +00:00
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/wait.h>
|
|
|
|
#include <syslog.h>
|
|
|
|
#include <sys/mount.h>
|
2011-08-15 23:58:08 +00:00
|
|
|
#include <glob.h>
|
2010-06-10 20:35:55 +00:00
|
|
|
#include <pwd.h>
|
|
|
|
#include <sched.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <stdio.h>
|
2011-08-03 19:09:22 +00:00
|
|
|
#include <regex.h>
|
2010-06-10 20:35:55 +00:00
|
|
|
#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>
|
2011-08-15 23:58:08 +00:00
|
|
|
#include <fcntl.h>
|
2010-06-10 20:35:55 +00:00
|
|
|
|
|
|
|
#include <selinux/selinux.h>
|
|
|
|
#include <selinux/context.h> /* for context-mangling functions */
|
2011-07-07 00:22:26 +00:00
|
|
|
#include <dirent.h>
|
2010-06-10 20:35:55 +00:00
|
|
|
|
|
|
|
#ifdef USE_NLS
|
|
|
|
#include <locale.h> /* for setlocale() */
|
|
|
|
#include <libintl.h> /* for gettext() */
|
|
|
|
#define _(msgid) gettext (msgid)
|
|
|
|
#else
|
|
|
|
#define _(msgid) (msgid)
|
|
|
|
#endif
|
|
|
|
|
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 20:37:59 +00:00
|
|
|
#ifndef MS_REC
|
|
|
|
#define MS_REC 1<<14
|
|
|
|
#endif
|
|
|
|
|
2012-01-03 18:45:08 +00:00
|
|
|
#ifndef MS_SLAVE
|
|
|
|
#define MS_SLAVE 1<<19
|
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 20:37:59 +00:00
|
|
|
#endif
|
|
|
|
|
2011-08-16 00:10:14 +00:00
|
|
|
#ifndef PACKAGE
|
|
|
|
#define PACKAGE "policycoreutils" /* the name of this package lang translation */
|
|
|
|
#endif
|
|
|
|
|
2011-08-03 19:09:22 +00:00
|
|
|
#define BUF_SIZE 1024
|
2011-08-03 20:23:12 +00:00
|
|
|
#define DEFAULT_PATH "/usr/bin:/bin"
|
2022-10-13 13:23:12 +00:00
|
|
|
#define USAGE_STRING _("USAGE: seunshare [ -v ] [ -C ] [ -k ] [ -t tmpdir ] [ -h homedir ] [ -r runuserdir ] [ -Z CONTEXT ] -- executable [args] ")
|
2011-08-03 20:23:12 +00:00
|
|
|
|
|
|
|
static int verbose = 0;
|
2011-07-07 00:52:05 +00:00
|
|
|
static int child = 0;
|
2011-08-03 20:23:12 +00:00
|
|
|
|
2012-02-22 20:55:39 +00:00
|
|
|
static capng_select_t cap_set = CAPNG_SELECT_CAPS;
|
2011-06-13 17:24:38 +00:00
|
|
|
|
2010-06-10 20:35:55 +00:00
|
|
|
/**
|
2011-08-05 17:33:35 +00:00
|
|
|
* This function will drop all capabilities.
|
2010-06-10 20:35:55 +00:00
|
|
|
*/
|
2014-09-14 21:41:49 +00:00
|
|
|
static int drop_caps(void)
|
2010-06-10 20:35:55 +00:00
|
|
|
{
|
2011-06-13 17:24:38 +00:00
|
|
|
if (capng_have_capabilities(cap_set) == CAPNG_NONE)
|
2011-08-05 17:33:35 +00:00
|
|
|
return 0;
|
2011-06-13 17:24:38 +00:00
|
|
|
capng_clear(cap_set);
|
|
|
|
if (capng_lock() == -1 || capng_apply(cap_set) == -1) {
|
2011-08-05 17:33:35 +00:00
|
|
|
fprintf(stderr, _("Failed to drop all capabilities\n"));
|
2010-06-10 20:35:55 +00:00
|
|
|
return -1;
|
2011-08-05 17:33:35 +00:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This function will drop all privileges.
|
|
|
|
*/
|
|
|
|
static int drop_privs(uid_t uid)
|
|
|
|
{
|
|
|
|
if (drop_caps() == -1 || setresuid(uid, uid, uid) == -1) {
|
|
|
|
fprintf(stderr, _("Failed to drop privileges\n"));
|
2010-06-10 20:35:55 +00:00
|
|
|
return -1;
|
|
|
|
}
|
2011-08-05 17:33:35 +00:00
|
|
|
return 0;
|
2010-06-10 20:35:55 +00:00
|
|
|
}
|
|
|
|
|
2011-07-07 00:52:05 +00:00
|
|
|
/**
|
|
|
|
* If the user sends a siginto to seunshare, kill the child's session
|
|
|
|
*/
|
2021-11-12 15:41:56 +00:00
|
|
|
static void handler(int sig) {
|
2011-07-07 00:52:05 +00:00
|
|
|
if (child > 0) kill(-child,sig);
|
|
|
|
}
|
|
|
|
|
2010-06-10 20:35:55 +00:00
|
|
|
/**
|
2011-08-03 18:27:32 +00:00
|
|
|
* Take care of any signal setup.
|
2010-06-10 20:35:55 +00:00
|
|
|
*/
|
|
|
|
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);
|
|
|
|
|
2011-08-15 23:58:08 +00:00
|
|
|
/* Terminate on SIGHUP */
|
2010-06-10 20:35:55 +00:00
|
|
|
if (signal(SIGHUP, SIG_DFL) == SIG_ERR) {
|
|
|
|
perror("Unable to set SIGHUP handler");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2011-07-07 00:52:05 +00:00
|
|
|
if (signal(SIGINT, handler) == SIG_ERR) {
|
|
|
|
perror("Unable to set SIGINT handler");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2010-06-10 20:35:55 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-08-05 18:06:34 +00:00
|
|
|
#define status_to_retval(status,retval) do { \
|
|
|
|
if ((status) == -1) \
|
|
|
|
retval = -1; \
|
|
|
|
else if (WIFEXITED((status))) \
|
|
|
|
retval = WEXITSTATUS((status)); \
|
|
|
|
else if (WIFSIGNALED((status))) \
|
|
|
|
retval = 128 + WTERMSIG((status)); \
|
|
|
|
else \
|
|
|
|
retval = -1; \
|
|
|
|
} while(0)
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Spawn external command using system() with dropped privileges.
|
|
|
|
* TODO: avoid system() and use exec*() instead
|
|
|
|
*/
|
|
|
|
static int spawn_command(const char *cmd, uid_t uid){
|
2014-09-14 21:41:35 +00:00
|
|
|
int childpid;
|
2011-08-05 18:06:34 +00:00
|
|
|
int status = -1;
|
|
|
|
|
|
|
|
if (verbose > 1)
|
|
|
|
printf("spawn_command: %s\n", cmd);
|
|
|
|
|
2014-09-14 21:41:35 +00:00
|
|
|
childpid = fork();
|
|
|
|
if (childpid == -1) {
|
2011-08-05 18:06:34 +00:00
|
|
|
perror(_("Unable to fork"));
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
2014-09-14 21:41:35 +00:00
|
|
|
if (childpid == 0) {
|
2011-08-05 18:06:34 +00:00
|
|
|
if (drop_privs(uid) != 0) exit(-1);
|
|
|
|
|
|
|
|
status = system(cmd);
|
|
|
|
status_to_retval(status, status);
|
|
|
|
exit(status);
|
|
|
|
}
|
|
|
|
|
2014-09-14 21:41:35 +00:00
|
|
|
waitpid(childpid, &status, 0);
|
2011-08-05 18:06:34 +00:00
|
|
|
status_to_retval(status, status);
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
2011-08-05 18:36:29 +00:00
|
|
|
/**
|
|
|
|
* Check file/directory ownership, struct stat * must be passed to the
|
|
|
|
* functions.
|
|
|
|
*/
|
|
|
|
static int check_owner_uid(uid_t uid, const char *file, struct stat *st) {
|
|
|
|
if (S_ISLNK(st->st_mode)) {
|
|
|
|
fprintf(stderr, _("Error: %s must not be a symbolic link\n"), file);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (st->st_uid != uid) {
|
|
|
|
fprintf(stderr, _("Error: %s not owned by UID %d\n"), file, uid);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int check_owner_gid(gid_t gid, const char *file, struct stat *st) {
|
|
|
|
if (S_ISLNK(st->st_mode)) {
|
|
|
|
fprintf(stderr, _("Error: %s must not be a symbolic link\n"), file);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (st->st_gid != gid) {
|
|
|
|
fprintf(stderr, _("Error: %s not owned by GID %d\n"), file, gid);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define equal_stats(one,two) \
|
|
|
|
((one)->st_dev == (two)->st_dev && (one)->st_ino == (two)->st_ino && \
|
|
|
|
(one)->st_uid == (two)->st_uid && (one)->st_gid == (two)->st_gid && \
|
|
|
|
(one)->st_mode == (two)->st_mode)
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sanity check specified directory. Store stat info for future comparison, or
|
|
|
|
* compare with previously saved info to detect replaced directories.
|
|
|
|
* Note: This function does not perform owner checks.
|
|
|
|
*/
|
|
|
|
static int verify_directory(const char *dir, struct stat *st_in, struct stat *st_out) {
|
|
|
|
struct stat sb;
|
|
|
|
|
|
|
|
if (st_out == NULL) st_out = &sb;
|
|
|
|
|
|
|
|
if (lstat(dir, st_out) == -1) {
|
|
|
|
fprintf(stderr, _("Failed to stat %s: %s\n"), dir, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (! S_ISDIR(st_out->st_mode)) {
|
|
|
|
fprintf(stderr, _("Error: %s is not a directory: %s\n"), dir, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (st_in && !equal_stats(st_in, st_out)) {
|
|
|
|
fprintf(stderr, _("Error: %s was replaced by a different directory\n"), dir);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-06-10 20:35:55 +00:00
|
|
|
/**
|
|
|
|
* 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)) {
|
2011-08-15 23:58:08 +00:00
|
|
|
rc = 0;
|
2010-06-10 20:35:55 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
endusershell();
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2011-08-15 23:58:08 +00:00
|
|
|
/**
|
|
|
|
* Mount directory and check that we mounted the right directory.
|
|
|
|
*/
|
|
|
|
static int seunshare_mount(const char *src, const char *dst, struct stat *src_st)
|
|
|
|
{
|
2012-01-03 18:45:08 +00:00
|
|
|
int flags = 0;
|
2011-08-15 23:58:08 +00:00
|
|
|
int is_tmp = 0;
|
|
|
|
|
2010-06-10 20:35:55 +00:00
|
|
|
if (verbose)
|
2011-08-15 23:58:08 +00:00
|
|
|
printf(_("Mounting %s on %s\n"), src, dst);
|
|
|
|
|
|
|
|
if (strcmp("/tmp", dst) == 0) {
|
|
|
|
flags = flags | MS_NODEV | MS_NOSUID | MS_NOEXEC;
|
|
|
|
is_tmp = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* mount directory */
|
|
|
|
if (mount(src, dst, NULL, MS_BIND | flags, NULL) < 0) {
|
2010-06-10 20:35:55 +00:00
|
|
|
fprintf(stderr, _("Failed to mount %s on %s: %s\n"), src, dst, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2011-08-15 23:58:08 +00:00
|
|
|
/* verify whether we mounted what we expected to mount */
|
|
|
|
if (verify_directory(dst, src_st, NULL) < 0) return -1;
|
|
|
|
|
|
|
|
/* bind mount /tmp on /var/tmp too */
|
|
|
|
if (is_tmp) {
|
|
|
|
if (verbose)
|
|
|
|
printf(_("Mounting /tmp on /var/tmp\n"));
|
|
|
|
|
|
|
|
if (mount("/tmp", "/var/tmp", NULL, MS_BIND | flags, NULL) < 0) {
|
|
|
|
fprintf(stderr, _("Failed to mount /tmp on /var/tmp: %s\n"), strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
2010-06-10 20:35:55 +00:00
|
|
|
}
|
|
|
|
|
2011-08-15 23:58:08 +00:00
|
|
|
/*
|
2019-08-05 20:11:20 +00:00
|
|
|
If path is empty or ends with "/." or "/.. return -1 else return 0;
|
2011-08-15 23:58:08 +00:00
|
|
|
*/
|
|
|
|
static int bad_path(const char *path) {
|
|
|
|
const char *ptr;
|
|
|
|
ptr = path;
|
|
|
|
while (*ptr) ptr++;
|
|
|
|
if (ptr == path) return -1; // ptr null
|
|
|
|
ptr--;
|
|
|
|
if (ptr != path && *ptr == '.') {
|
|
|
|
ptr--;
|
|
|
|
if (*ptr == '/') return -1; // path ends in /.
|
|
|
|
if (*ptr == '.') {
|
|
|
|
if (ptr != path) {
|
|
|
|
ptr--;
|
|
|
|
if (*ptr == '/') return -1; // path ends in /..
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int rsynccmd(const char * src, const char *dst, char **cmdbuf)
|
|
|
|
{
|
|
|
|
char *buf = NULL;
|
|
|
|
char *newbuf = NULL;
|
|
|
|
glob_t fglob;
|
|
|
|
fglob.gl_offs = 0;
|
|
|
|
int flags = GLOB_PERIOD;
|
|
|
|
unsigned int i = 0;
|
|
|
|
int rc = -1;
|
|
|
|
|
|
|
|
/* match glob for all files in src dir */
|
|
|
|
if (asprintf(&buf, "%s/*", src) == -1) {
|
|
|
|
fprintf(stderr, "Out of memory\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (glob(buf, flags, NULL, &fglob) != 0) {
|
|
|
|
free(buf); buf = NULL;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
free(buf); buf = NULL;
|
|
|
|
|
|
|
|
for ( i=0; i < fglob.gl_pathc; i++) {
|
|
|
|
const char *path = fglob.gl_pathv[i];
|
|
|
|
|
|
|
|
if (bad_path(path)) continue;
|
|
|
|
|
|
|
|
if (!buf) {
|
|
|
|
if (asprintf(&newbuf, "\'%s\'", path) == -1) {
|
|
|
|
fprintf(stderr, "Out of memory\n");
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (asprintf(&newbuf, "%s \'%s\'", buf, path) == -1) {
|
|
|
|
fprintf(stderr, "Out of memory\n");
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
free(buf); buf = newbuf;
|
|
|
|
newbuf = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (buf) {
|
|
|
|
if (asprintf(&newbuf, "/usr/bin/rsync -trlHDq %s '%s'", buf, dst) == -1) {
|
|
|
|
fprintf(stderr, "Out of memory\n");
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
*cmdbuf=newbuf;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
*cmdbuf=NULL;
|
|
|
|
}
|
|
|
|
rc = 0;
|
|
|
|
|
|
|
|
err:
|
|
|
|
free(buf); buf = NULL;
|
|
|
|
globfree(&fglob);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Clean up runtime temporary directory. Returns 0 if no problem was detected,
|
|
|
|
* >0 if some error was detected, but errors here are treated as non-fatal and
|
|
|
|
* left to tmpwatch to finish incomplete cleanup.
|
|
|
|
*/
|
|
|
|
static int cleanup_tmpdir(const char *tmpdir, const char *src,
|
|
|
|
struct passwd *pwd, int copy_content)
|
|
|
|
{
|
|
|
|
char *cmdbuf = NULL;
|
|
|
|
int rc = 0;
|
|
|
|
|
|
|
|
/* rsync files back */
|
|
|
|
if (copy_content) {
|
|
|
|
if (asprintf(&cmdbuf, "/usr/bin/rsync --exclude=.X11-unix -utrlHDq --delete '%s/' '%s/'", tmpdir, src) == -1) {
|
|
|
|
fprintf(stderr, _("Out of memory\n"));
|
|
|
|
cmdbuf = NULL;
|
|
|
|
rc++;
|
|
|
|
}
|
|
|
|
if (cmdbuf && spawn_command(cmdbuf, pwd->pw_uid) != 0) {
|
|
|
|
fprintf(stderr, _("Failed to copy files from the runtime temporary directory\n"));
|
|
|
|
rc++;
|
|
|
|
}
|
|
|
|
free(cmdbuf); cmdbuf = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* remove files from the runtime temporary directory */
|
|
|
|
if (asprintf(&cmdbuf, "/bin/rm -r '%s/' 2>/dev/null", tmpdir) == -1) {
|
|
|
|
fprintf(stderr, _("Out of memory\n"));
|
|
|
|
cmdbuf = NULL;
|
|
|
|
rc++;
|
|
|
|
}
|
|
|
|
/* this may fail if there's root-owned file left in the runtime tmpdir */
|
|
|
|
if (cmdbuf && spawn_command(cmdbuf, pwd->pw_uid) != 0) rc++;
|
|
|
|
free(cmdbuf); cmdbuf = NULL;
|
|
|
|
|
|
|
|
/* remove runtime temporary directory */
|
2013-10-09 21:28:37 +00:00
|
|
|
if ((uid_t)setfsuid(0) != 0) {
|
2019-08-05 20:11:20 +00:00
|
|
|
/* setfsuid does not return error, but this check makes code checkers happy */
|
2012-09-26 15:00:56 +00:00
|
|
|
rc++;
|
|
|
|
}
|
|
|
|
|
2022-10-13 13:23:11 +00:00
|
|
|
if (pwd->pw_uid != 0 && rmdir(tmpdir) == -1)
|
2011-08-15 23:58:08 +00:00
|
|
|
fprintf(stderr, _("Failed to remove directory %s: %s\n"), tmpdir, strerror(errno));
|
2012-09-26 15:00:56 +00:00
|
|
|
if ((uid_t)setfsuid(pwd->pw_uid) != 0) {
|
|
|
|
fprintf(stderr, _("unable to switch back to user after clearing tmp dir\n"));
|
|
|
|
rc++;
|
|
|
|
}
|
2011-08-15 23:58:08 +00:00
|
|
|
|
2012-09-26 15:00:56 +00:00
|
|
|
return rc;
|
2011-08-15 23:58:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* seunshare will create a tmpdir in /tmp, with root ownership. The parent
|
|
|
|
* process waits for it child to exit to attempt to remove the directory. If
|
|
|
|
* it fails to remove the directory, we will need to rely on tmpreaper/tmpwatch
|
|
|
|
* to clean it up.
|
|
|
|
*/
|
|
|
|
static char *create_tmpdir(const char *src, struct stat *src_st,
|
2020-03-23 18:31:30 +00:00
|
|
|
struct stat *out_st, struct passwd *pwd, const char *execcon)
|
2011-08-15 23:58:08 +00:00
|
|
|
{
|
|
|
|
char *tmpdir = NULL;
|
|
|
|
char *cmdbuf = NULL;
|
|
|
|
int fd_t = -1, fd_s = -1;
|
|
|
|
struct stat tmp_st;
|
2020-03-23 18:31:30 +00:00
|
|
|
char *con = NULL;
|
2011-08-15 23:58:08 +00:00
|
|
|
|
|
|
|
/* get selinux context */
|
|
|
|
if (execcon) {
|
2012-09-26 15:00:56 +00:00
|
|
|
if ((uid_t)setfsuid(pwd->pw_uid) != 0)
|
|
|
|
goto err;
|
|
|
|
|
2011-08-15 23:58:08 +00:00
|
|
|
if ((fd_s = open(src, O_RDONLY)) < 0) {
|
|
|
|
fprintf(stderr, _("Failed to open directory %s: %s\n"), src, strerror(errno));
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
if (fstat(fd_s, &tmp_st) == -1) {
|
|
|
|
fprintf(stderr, _("Failed to stat directory %s: %s\n"), src, strerror(errno));
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
if (!equal_stats(src_st, &tmp_st)) {
|
|
|
|
fprintf(stderr, _("Error: %s was replaced by a different directory\n"), src);
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
if (fgetfilecon(fd_s, &con) == -1) {
|
|
|
|
fprintf(stderr, _("Failed to get context of the directory %s: %s\n"), src, strerror(errno));
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ok to not reach this if there is an error */
|
2012-09-26 15:00:56 +00:00
|
|
|
if ((uid_t)setfsuid(0) != pwd->pw_uid)
|
|
|
|
goto err;
|
2011-08-15 23:58:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (asprintf(&tmpdir, "/tmp/.sandbox-%s-XXXXXX", pwd->pw_name) == -1) {
|
|
|
|
fprintf(stderr, _("Out of memory\n"));
|
|
|
|
tmpdir = NULL;
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
if (mkdtemp(tmpdir) == NULL) {
|
|
|
|
fprintf(stderr, _("Failed to create temporary directory: %s\n"), strerror(errno));
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* temporary directory must be owned by root:user */
|
|
|
|
if (verify_directory(tmpdir, NULL, out_st) < 0) {
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (check_owner_uid(0, tmpdir, out_st) < 0)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
if (check_owner_gid(getgid(), tmpdir, out_st) < 0)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
/* change permissions of the temporary directory */
|
|
|
|
if ((fd_t = open(tmpdir, O_RDONLY)) < 0) {
|
|
|
|
fprintf(stderr, _("Failed to open directory %s: %s\n"), tmpdir, strerror(errno));
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
if (fstat(fd_t, &tmp_st) == -1) {
|
|
|
|
fprintf(stderr, _("Failed to stat directory %s: %s\n"), tmpdir, strerror(errno));
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
if (!equal_stats(out_st, &tmp_st)) {
|
|
|
|
fprintf(stderr, _("Error: %s was replaced by a different directory\n"), tmpdir);
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
if (fchmod(fd_t, 01770) == -1) {
|
|
|
|
fprintf(stderr, _("Unable to change mode on %s: %s\n"), tmpdir, strerror(errno));
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
/* re-stat again to pick change mode */
|
|
|
|
if (fstat(fd_t, out_st) == -1) {
|
|
|
|
fprintf(stderr, _("Failed to stat directory %s: %s\n"), tmpdir, strerror(errno));
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* copy selinux context */
|
|
|
|
if (execcon) {
|
|
|
|
if (fsetfilecon(fd_t, con) == -1) {
|
|
|
|
fprintf(stderr, _("Failed to set context of the directory %s: %s\n"), tmpdir, strerror(errno));
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-09-26 15:00:56 +00:00
|
|
|
if ((uid_t)setfsuid(pwd->pw_uid) != 0)
|
|
|
|
goto err;
|
2011-08-15 23:58:08 +00:00
|
|
|
|
|
|
|
if (rsynccmd(src, tmpdir, &cmdbuf) < 0) {
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ok to not reach this if there is an error */
|
2012-09-26 15:00:56 +00:00
|
|
|
if ((uid_t)setfsuid(0) != pwd->pw_uid)
|
|
|
|
goto err;
|
2011-08-15 23:58:08 +00:00
|
|
|
|
|
|
|
if (cmdbuf && spawn_command(cmdbuf, pwd->pw_uid) != 0) {
|
|
|
|
fprintf(stderr, _("Failed to populate runtime temporary directory\n"));
|
|
|
|
cleanup_tmpdir(tmpdir, src, pwd, 0);
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
goto good;
|
|
|
|
err:
|
|
|
|
free(tmpdir); tmpdir = NULL;
|
|
|
|
good:
|
|
|
|
free(cmdbuf); cmdbuf = NULL;
|
|
|
|
freecon(con); con = NULL;
|
|
|
|
if (fd_t >= 0) close(fd_t);
|
|
|
|
if (fd_s >= 0) close(fd_s);
|
|
|
|
return tmpdir;
|
|
|
|
}
|
|
|
|
|
2011-07-07 00:22:26 +00:00
|
|
|
#define PROC_BASE "/proc"
|
|
|
|
|
|
|
|
static int
|
2020-03-23 18:31:30 +00:00
|
|
|
killall (const char *execcon)
|
2011-07-07 00:22:26 +00:00
|
|
|
{
|
|
|
|
DIR *dir;
|
2020-03-23 18:31:30 +00:00
|
|
|
char *scon;
|
2011-07-07 00:22:26 +00:00
|
|
|
struct dirent *de;
|
|
|
|
pid_t *pid_table, pid, self;
|
|
|
|
int i;
|
|
|
|
int pids, max_pids;
|
|
|
|
int running = 0;
|
|
|
|
self = getpid();
|
|
|
|
if (!(dir = opendir(PROC_BASE))) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
max_pids = 256;
|
|
|
|
pid_table = malloc(max_pids * sizeof (pid_t));
|
|
|
|
if (!pid_table) {
|
|
|
|
(void)closedir(dir);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
pids = 0;
|
|
|
|
context_t con;
|
|
|
|
con = context_new(execcon);
|
|
|
|
const char *mcs = context_range_get(con);
|
|
|
|
printf("mcs=%s\n", mcs);
|
|
|
|
while ((de = readdir (dir)) != NULL) {
|
|
|
|
if (!(pid = (pid_t)atoi(de->d_name)) || pid == self)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (pids == max_pids) {
|
2013-02-01 20:23:12 +00:00
|
|
|
pid_t *new_pid_table = realloc(pid_table, 2*pids*sizeof(pid_t));
|
|
|
|
if (!new_pid_table) {
|
|
|
|
free(pid_table);
|
2011-07-07 00:22:26 +00:00
|
|
|
(void)closedir(dir);
|
|
|
|
return -1;
|
|
|
|
}
|
2013-02-01 20:23:12 +00:00
|
|
|
pid_table = new_pid_table;
|
2011-07-07 00:22:26 +00:00
|
|
|
max_pids *= 2;
|
|
|
|
}
|
|
|
|
pid_table[pids++] = pid;
|
|
|
|
}
|
|
|
|
|
|
|
|
(void)closedir(dir);
|
|
|
|
|
|
|
|
for (i = 0; i < pids; i++) {
|
|
|
|
pid_t id = pid_table[i];
|
|
|
|
|
|
|
|
if (getpidcon(id, &scon) == 0) {
|
|
|
|
|
|
|
|
context_t pidcon = context_new(scon);
|
|
|
|
/* Attempt to kill remaining processes */
|
|
|
|
if (strcmp(context_range_get(pidcon), mcs) == 0)
|
|
|
|
kill(id, SIGKILL);
|
|
|
|
|
|
|
|
context_free(pidcon);
|
|
|
|
freecon(scon);
|
|
|
|
}
|
|
|
|
running++;
|
|
|
|
}
|
|
|
|
|
|
|
|
context_free(con);
|
|
|
|
free(pid_table);
|
|
|
|
return running;
|
|
|
|
}
|
|
|
|
|
2010-06-10 20:35:55 +00:00
|
|
|
int main(int argc, char **argv) {
|
|
|
|
int status = -1;
|
2020-03-23 18:31:30 +00:00
|
|
|
const char *execcon = NULL;
|
2010-06-10 20:35:55 +00:00
|
|
|
|
|
|
|
int clflag; /* holds codes for command line flags */
|
2011-07-07 00:22:26 +00:00
|
|
|
int kill_all = 0;
|
2010-06-10 20:35:55 +00:00
|
|
|
|
2011-08-15 23:58:08 +00:00
|
|
|
char *homedir_s = NULL; /* homedir spec'd by user in argv[] */
|
|
|
|
char *tmpdir_s = NULL; /* tmpdir spec'd by user in argv[] */
|
|
|
|
char *tmpdir_r = NULL; /* tmpdir created by seunshare */
|
2022-10-13 13:23:12 +00:00
|
|
|
char *runuserdir_s = NULL; /* /var/run/user/UID spec'd by user in argv[] */
|
|
|
|
char *runuserdir_r = NULL; /* /var/run/user/UID created by seunshare */
|
2011-08-15 23:58:08 +00:00
|
|
|
|
2013-10-09 21:28:37 +00:00
|
|
|
struct stat st_curhomedir;
|
2011-08-15 23:58:08 +00:00
|
|
|
struct stat st_homedir;
|
|
|
|
struct stat st_tmpdir_s;
|
|
|
|
struct stat st_tmpdir_r;
|
2022-10-13 13:23:12 +00:00
|
|
|
struct stat st_runuserdir_s;
|
|
|
|
struct stat st_runuserdir_r;
|
2011-08-15 23:58:08 +00:00
|
|
|
|
2010-06-10 20:35:55 +00:00
|
|
|
const struct option long_options[] = {
|
|
|
|
{"homedir", 1, 0, 'h'},
|
|
|
|
{"tmpdir", 1, 0, 't'},
|
2022-10-13 13:23:12 +00:00
|
|
|
{"runuserdir", 1, 0, 'r'},
|
2011-07-07 00:22:26 +00:00
|
|
|
{"kill", 1, 0, 'k'},
|
2010-06-10 20:35:55 +00:00
|
|
|
{"verbose", 1, 0, 'v'},
|
2011-08-15 20:00:04 +00:00
|
|
|
{"context", 1, 0, 'Z'},
|
2011-06-13 17:24:38 +00:00
|
|
|
{"capabilities", 1, 0, 'C'},
|
2010-06-10 20:35:55 +00:00
|
|
|
{NULL, 0, 0, 0}
|
|
|
|
};
|
|
|
|
|
|
|
|
uid_t uid = getuid();
|
2011-07-26 14:42:26 +00:00
|
|
|
/*
|
2010-06-10 20:35:55 +00:00
|
|
|
if (!uid) {
|
|
|
|
fprintf(stderr, _("Must not be root"));
|
|
|
|
return -1;
|
|
|
|
}
|
2011-07-26 14:42:26 +00:00
|
|
|
*/
|
2010-06-10 20:35:55 +00:00
|
|
|
|
2011-08-16 00:10:14 +00:00
|
|
|
#ifdef USE_NLS
|
|
|
|
setlocale(LC_ALL, "");
|
|
|
|
bindtextdomain(PACKAGE, LOCALEDIR);
|
|
|
|
textdomain(PACKAGE);
|
|
|
|
#endif
|
|
|
|
|
2010-06-10 20:35:55 +00:00
|
|
|
struct passwd *pwd=getpwuid(uid);
|
|
|
|
if (!pwd) {
|
|
|
|
perror(_("getpwduid failed"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (verify_shell(pwd->pw_shell) < 0) {
|
2011-08-15 23:58:08 +00:00
|
|
|
fprintf(stderr, _("Error: User shell is not valid\n"));
|
2010-06-10 20:35:55 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (1) {
|
2022-10-13 13:23:12 +00:00
|
|
|
clflag = getopt_long(argc, argv, "Ccvh:r:t:Z:", long_options, NULL);
|
2010-06-10 20:35:55 +00:00
|
|
|
if (clflag == -1)
|
|
|
|
break;
|
|
|
|
|
|
|
|
switch (clflag) {
|
|
|
|
case 't':
|
2011-08-15 23:58:08 +00:00
|
|
|
tmpdir_s = optarg;
|
2010-06-10 20:35:55 +00:00
|
|
|
break;
|
2011-07-07 00:22:26 +00:00
|
|
|
case 'k':
|
|
|
|
kill_all = 1;
|
|
|
|
break;
|
2010-06-10 20:35:55 +00:00
|
|
|
case 'h':
|
2011-08-15 23:58:08 +00:00
|
|
|
homedir_s = optarg;
|
2010-06-10 20:35:55 +00:00
|
|
|
break;
|
2022-10-13 13:23:12 +00:00
|
|
|
case 'r':
|
|
|
|
runuserdir_s = optarg;
|
|
|
|
break;
|
2010-06-10 20:35:55 +00:00
|
|
|
case 'v':
|
2011-08-15 23:58:08 +00:00
|
|
|
verbose++;
|
2010-06-10 20:35:55 +00:00
|
|
|
break;
|
2011-06-13 17:24:38 +00:00
|
|
|
case 'C':
|
|
|
|
cap_set = CAPNG_SELECT_CAPS;
|
|
|
|
break;
|
2011-08-15 20:00:04 +00:00
|
|
|
case 'Z':
|
2011-08-15 23:58:08 +00:00
|
|
|
execcon = optarg;
|
2011-08-15 20:00:04 +00:00
|
|
|
break;
|
2010-06-10 20:35:55 +00:00
|
|
|
default:
|
|
|
|
fprintf(stderr, "%s\n", USAGE_STRING);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (! homedir_s && ! tmpdir_s) {
|
2011-08-15 23:58:08 +00:00
|
|
|
fprintf(stderr, _("Error: tmpdir and/or homedir required\n %s\n"), USAGE_STRING);
|
2010-06-10 20:35:55 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2011-08-15 20:00:04 +00:00
|
|
|
if (argc - optind < 1) {
|
2011-08-15 23:58:08 +00:00
|
|
|
fprintf(stderr, _("Error: executable required\n %s\n"), USAGE_STRING);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (execcon && is_selinux_enabled() != 1) {
|
|
|
|
fprintf(stderr, _("Error: execution context specified, but SELinux is not enabled\n"));
|
2010-06-10 20:35:55 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (set_signal_handles())
|
|
|
|
return -1;
|
|
|
|
|
2011-08-15 23:58:08 +00:00
|
|
|
/* set fsuid to ruid */
|
|
|
|
/* Changing fsuid is usually required when user-specified directory is
|
|
|
|
* on an NFS mount. It's also desired to avoid leaking info about
|
|
|
|
* existence of the files not accessible to the user. */
|
2013-10-09 21:28:37 +00:00
|
|
|
if (((uid_t)setfsuid(uid) != 0) && (errno != 0)) {
|
|
|
|
fprintf(stderr, _("Error: unable to setfsuid %m\n"));
|
|
|
|
|
2012-09-26 15:00:56 +00:00
|
|
|
return -1;
|
2013-10-09 21:28:37 +00:00
|
|
|
}
|
2011-08-15 23:58:08 +00:00
|
|
|
|
|
|
|
/* verify homedir and tmpdir */
|
|
|
|
if (homedir_s && (
|
|
|
|
verify_directory(homedir_s, NULL, &st_homedir) < 0 ||
|
|
|
|
check_owner_uid(uid, homedir_s, &st_homedir))) return -1;
|
|
|
|
if (tmpdir_s && (
|
|
|
|
verify_directory(tmpdir_s, NULL, &st_tmpdir_s) < 0 ||
|
|
|
|
check_owner_uid(uid, tmpdir_s, &st_tmpdir_s))) return -1;
|
2022-10-13 13:23:12 +00:00
|
|
|
if (runuserdir_s && (
|
|
|
|
verify_directory(runuserdir_s, NULL, &st_runuserdir_s) < 0 ||
|
|
|
|
check_owner_uid(uid, runuserdir_s, &st_runuserdir_s))) return -1;
|
|
|
|
|
2012-09-26 15:00:56 +00:00
|
|
|
if ((uid_t)setfsuid(0) != uid) return -1;
|
2011-08-15 23:58:08 +00:00
|
|
|
|
|
|
|
/* create runtime tmpdir */
|
|
|
|
if (tmpdir_s && (tmpdir_r = create_tmpdir(tmpdir_s, &st_tmpdir_s,
|
|
|
|
&st_tmpdir_r, pwd, execcon)) == NULL) {
|
|
|
|
fprintf(stderr, _("Failed to create runtime temporary directory\n"));
|
2010-06-10 20:35:55 +00:00
|
|
|
return -1;
|
|
|
|
}
|
2022-10-13 13:23:12 +00:00
|
|
|
/* create runtime runuserdir */
|
|
|
|
if (runuserdir_s && (runuserdir_r = create_tmpdir(runuserdir_s, &st_runuserdir_s,
|
|
|
|
&st_runuserdir_r, pwd, execcon)) == NULL) {
|
|
|
|
fprintf(stderr, _("Failed to create runtime $XDG_RUNTIME_DIR directory\n"));
|
|
|
|
return -1;
|
|
|
|
}
|
2010-06-10 20:35:55 +00:00
|
|
|
|
2011-08-15 23:58:08 +00:00
|
|
|
/* spawn child process */
|
2011-07-07 00:52:05 +00:00
|
|
|
child = fork();
|
2010-06-10 20:35:55 +00:00
|
|
|
if (child == -1) {
|
|
|
|
perror(_("Unable to fork"));
|
2011-08-15 23:58:08 +00:00
|
|
|
goto err;
|
2010-06-10 20:35:55 +00:00
|
|
|
}
|
|
|
|
|
2011-08-15 23:58:08 +00:00
|
|
|
if (child == 0) {
|
|
|
|
char *display = NULL;
|
2011-09-07 18:20:30 +00:00
|
|
|
char *LANG = NULL;
|
2014-05-12 17:19:19 +00:00
|
|
|
char *RUNTIME_DIR = NULL;
|
2011-08-15 23:58:08 +00:00
|
|
|
int rc = -1;
|
2013-10-09 21:28:37 +00:00
|
|
|
char *resolved_path = NULL;
|
2010-06-10 20:35:55 +00:00
|
|
|
|
2011-08-15 23:58:08 +00:00
|
|
|
if (unshare(CLONE_NEWNS) < 0) {
|
|
|
|
perror(_("Failed to unshare"));
|
|
|
|
goto childerr;
|
2012-01-03 18:45:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Remount / as SLAVE so that nothing mounted in the namespace
|
|
|
|
shows up in the parent */
|
|
|
|
if (mount("none", "/", NULL, MS_SLAVE | MS_REC , NULL) < 0) {
|
|
|
|
perror(_("Failed to make / a SLAVE mountpoint\n"));
|
|
|
|
goto childerr;
|
2010-06-10 20:35:55 +00:00
|
|
|
}
|
2011-08-15 20:00:04 +00:00
|
|
|
|
2011-08-15 23:58:08 +00:00
|
|
|
/* assume fsuid==ruid after this point */
|
2012-09-26 15:00:56 +00:00
|
|
|
if ((uid_t)setfsuid(uid) != 0) goto childerr;
|
2011-08-15 23:58:08 +00:00
|
|
|
|
2013-10-09 21:28:37 +00:00
|
|
|
resolved_path = realpath(pwd->pw_dir,NULL);
|
|
|
|
if (! resolved_path) goto childerr;
|
|
|
|
|
|
|
|
if (verify_directory(resolved_path, NULL, &st_curhomedir) < 0)
|
|
|
|
goto childerr;
|
|
|
|
if (check_owner_uid(uid, resolved_path, &st_curhomedir) < 0)
|
|
|
|
goto childerr;
|
|
|
|
|
2022-10-13 13:23:12 +00:00
|
|
|
if ((RUNTIME_DIR = getenv("XDG_RUNTIME_DIR")) != NULL) {
|
|
|
|
if ((RUNTIME_DIR = strdup(RUNTIME_DIR)) == NULL) {
|
|
|
|
perror(_("Out of memory"));
|
|
|
|
goto childerr;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (asprintf(&RUNTIME_DIR, "/run/user/%d", uid) == -1) {
|
|
|
|
perror(_("Out of memory\n"));
|
|
|
|
goto childerr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* mount homedir, runuserdir and tmpdir, in this order */
|
|
|
|
if (runuserdir_s && seunshare_mount(runuserdir_s, RUNTIME_DIR,
|
|
|
|
&st_runuserdir_s) != 0) goto childerr;
|
2013-10-09 21:28:37 +00:00
|
|
|
if (homedir_s && seunshare_mount(homedir_s, resolved_path,
|
2011-08-15 23:58:08 +00:00
|
|
|
&st_homedir) != 0) goto childerr;
|
|
|
|
if (tmpdir_s && seunshare_mount(tmpdir_r, "/tmp",
|
|
|
|
&st_tmpdir_r) != 0) goto childerr;
|
|
|
|
|
|
|
|
if (drop_privs(uid) != 0) goto childerr;
|
|
|
|
|
|
|
|
/* construct a new environment */
|
|
|
|
if ((display = getenv("DISPLAY")) != NULL) {
|
|
|
|
if ((display = strdup(display)) == NULL) {
|
|
|
|
perror(_("Out of memory"));
|
|
|
|
goto childerr;
|
2011-08-15 20:00:04 +00:00
|
|
|
}
|
2010-06-10 20:35:55 +00:00
|
|
|
}
|
2012-09-26 15:00:56 +00:00
|
|
|
|
2011-09-07 18:20:30 +00:00
|
|
|
/* construct a new environment */
|
|
|
|
if ((LANG = getenv("LANG")) != NULL) {
|
|
|
|
if ((LANG = strdup(LANG)) == NULL) {
|
|
|
|
perror(_("Out of memory"));
|
|
|
|
goto childerr;
|
|
|
|
}
|
|
|
|
}
|
2012-09-26 15:00:56 +00:00
|
|
|
|
2011-08-15 23:58:08 +00:00
|
|
|
if ((rc = clearenv()) != 0) {
|
|
|
|
perror(_("Failed to clear environment"));
|
|
|
|
goto childerr;
|
|
|
|
}
|
|
|
|
if (display)
|
2010-06-10 20:35:55 +00:00
|
|
|
rc |= setenv("DISPLAY", display, 1);
|
2012-09-26 15:00:56 +00:00
|
|
|
if (LANG)
|
2011-09-07 18:20:30 +00:00
|
|
|
rc |= setenv("LANG", LANG, 1);
|
2014-05-12 17:19:19 +00:00
|
|
|
if (RUNTIME_DIR)
|
|
|
|
rc |= setenv("XDG_RUNTIME_DIR", RUNTIME_DIR, 1);
|
2010-06-10 20:35:55 +00:00
|
|
|
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);
|
2011-08-15 23:58:08 +00:00
|
|
|
if (rc != 0) {
|
|
|
|
fprintf(stderr, _("Failed to construct environment\n"));
|
|
|
|
goto childerr;
|
|
|
|
}
|
|
|
|
|
2010-06-10 20:35:55 +00:00
|
|
|
if (chdir(pwd->pw_dir)) {
|
|
|
|
perror(_("Failed to change dir to homedir"));
|
2011-08-15 23:58:08 +00:00
|
|
|
goto childerr;
|
2010-06-10 20:35:55 +00:00
|
|
|
}
|
|
|
|
setsid();
|
2014-05-12 17:19:21 +00:00
|
|
|
|
|
|
|
/* selinux context */
|
|
|
|
if (execcon) {
|
|
|
|
/* try dyntransition, since no_new_privs can interfere
|
|
|
|
* with setexeccon */
|
|
|
|
if (setcon(execcon) != 0) {
|
|
|
|
/* failed; fall back to setexeccon */
|
|
|
|
if (setexeccon(execcon) != 0) {
|
|
|
|
fprintf(stderr, _("Could not set exec context to %s. %s\n"), execcon, strerror(errno));
|
|
|
|
goto childerr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-06-10 20:35:55 +00:00
|
|
|
execv(argv[optind], argv + optind);
|
2011-08-15 23:58:08 +00:00
|
|
|
fprintf(stderr, _("Failed to execute command %s: %s\n"), argv[optind], strerror(errno));
|
|
|
|
childerr:
|
2013-10-09 21:28:37 +00:00
|
|
|
free(resolved_path);
|
2010-06-10 20:35:55 +00:00
|
|
|
free(display);
|
2011-09-07 18:20:30 +00:00
|
|
|
free(LANG);
|
2014-05-12 17:19:19 +00:00
|
|
|
free(RUNTIME_DIR);
|
2010-06-10 20:35:55 +00:00
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
|
2011-08-15 23:58:08 +00:00
|
|
|
drop_caps();
|
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 20:37:59 +00:00
|
|
|
|
2011-08-15 23:58:08 +00:00
|
|
|
/* parent waits for child exit to do the cleanup */
|
|
|
|
waitpid(child, &status, 0);
|
|
|
|
status_to_retval(status, status);
|
|
|
|
|
2011-07-07 00:52:05 +00:00
|
|
|
/* Make sure all child processes exit */
|
|
|
|
kill(-child,SIGTERM);
|
|
|
|
|
2011-07-07 00:22:26 +00:00
|
|
|
if (execcon && kill_all)
|
|
|
|
killall(execcon);
|
|
|
|
|
2011-08-15 23:58:08 +00:00
|
|
|
if (tmpdir_r) cleanup_tmpdir(tmpdir_r, tmpdir_s, pwd, 1);
|
|
|
|
|
|
|
|
err:
|
|
|
|
free(tmpdir_r);
|
2010-06-10 20:35:55 +00:00
|
|
|
return status;
|
|
|
|
}
|