222 changes: 155 additions & 67 deletions src/copy.c
Expand Up @@ -61,6 +61,7 @@
#include "write-any-file.h"
#include "areadlink.h"
#include "yesno.h"
#include "selinux.h"

#if USE_XATTR
# include <attr/error_context.h>
Expand Down Expand Up @@ -512,6 +513,18 @@ copy_attr_free (struct error_context *ctx _GL_UNUSED,
{
}

/* Exclude SELinux extended attributes that are otherwise handled,
and are problematic to copy again. Also honor attributes
configured for exclusion in /etc/xattr.conf.
FIXME: Should we handle POSIX ACLs similarly?
Return zero to skip. */
static int
check_selinux_attr (const char *name, struct error_context *ctx)
{
return STRNCMP_LIT (name, "security.selinux")
&& attr_copy_check_permissions (name, ctx);
}

/* If positive SRC_FD and DST_FD descriptors are passed,
then copy by fd, otherwise copy by name. */

Expand All @@ -522,17 +535,20 @@ copy_attr (char const *src_path, int src_fd,
int ret;
bool all_errors = (!x->data_copy_required || x->require_preserve_xattr);
bool some_errors = (!all_errors && !x->reduce_diagnostics);
bool selinux_done = (x->preserve_security_context || x->set_security_context);
struct error_context ctx =
{
.error = all_errors ? copy_attr_allerror : copy_attr_error,
.quote = copy_attr_quote,
.quote_free = copy_attr_free
};
if (0 <= src_fd && 0 <= dst_fd)
ret = attr_copy_fd (src_path, src_fd, dst_path, dst_fd, 0,
ret = attr_copy_fd (src_path, src_fd, dst_path, dst_fd,
selinux_done ? check_selinux_attr : NULL,
(all_errors || some_errors ? &ctx : NULL));
else
ret = attr_copy_file (src_path, dst_path, 0,
ret = attr_copy_file (src_path, dst_path,
selinux_done ? check_selinux_attr : NULL,
(all_errors || some_errors ? &ctx : NULL));

return ret == 0;
Expand Down Expand Up @@ -737,6 +753,96 @@ set_author (const char *dst_name, int dest_desc, const struct stat *src_sb)
#endif
}

/* Set the default security context for the process. New files will
have this security context set. Also existing files can have their
context adjusted based on this process context, by
set_file_security_ctx() called with PROCESS_LOCAL=true.
This should be called before files are created so there is no race
where a file may be present without an appropriate security context.
Based on CP_OPTIONS, diagnose warnings and fail when appropriate.
Return FALSE on failure, TRUE on success. */

static bool
set_process_security_ctx (char const *src_name, char const *dst_name,
mode_t mode, bool new_dst, const struct cp_options *x)
{
if (x->preserve_security_context)
{
/* Set the default context for the process to match the source. */
bool all_errors = !x->data_copy_required || x->require_preserve_context;
bool some_errors = !all_errors && !x->reduce_diagnostics;
security_context_t con;

if (0 <= lgetfilecon (src_name, &con))
{
if (setfscreatecon (con) < 0)
{
if (all_errors || (some_errors && !errno_unsupported (errno)))
error (0, errno,
_("failed to set default file creation context to %s"),
quote (con));
if (x->require_preserve_context)
{
freecon (con);
return false;
}
}
freecon (con);
}
else
{
if (all_errors || (some_errors && !errno_unsupported (errno)))
{
error (0, errno,
_("failed to get security context of %s"),
quote (src_name));
}
if (x->require_preserve_context)
return false;
}
}
else if (x->set_security_context)
{
/* With -Z, adjust the default context for the process
to have the type component adjusted as per the destination path. */
if (new_dst && defaultcon (dst_name, mode) < 0)
{
if (!errno_unsupported (errno))
error (0, errno,
_("failed to set default file creation context for %s"),
quote (dst_name));
}
}

return true;
}

/* Reset the security context of DST_NAME, to that already set
as the process default if PROCESS_LOCAL is true. Otherwise
adjust the type component of DST_NAME's security context as
per the system default for that path. Issue warnings upon
failure, when allowed by various settings in CP_OPTIONS.
Return FALSE on failure, TRUE on success. */

static bool
set_file_security_ctx (char const *dst_name, bool process_local,
bool recurse, const struct cp_options *x)
{
bool all_errors = (!x->data_copy_required
|| x->require_preserve_context);
bool some_errors = !all_errors && !x->reduce_diagnostics;

if (! restorecon (dst_name, recurse, process_local))
{
if (all_errors || (some_errors && !errno_unsupported (errno)))
error (0, errno, _("failed to set the security context of %s"),
quote_n (0, dst_name));
return false;
}

return true;
}

/* Change the file mode bits of the file identified by DESC or NAME to MODE.
Use DESC if DESC is valid and fchmod is available, NAME otherwise. */

Expand Down Expand Up @@ -834,45 +940,24 @@ copy_reg (char const *src_name, char const *dst_name,
dest_errno = errno;

/* When using cp --preserve=context to copy to an existing destination,
use the default context rather than that of the source. Why?
1) the src context may prohibit writing, and
2) because it's more consistent to use the same context
that is used when the destination file doesn't already exist. */
if (x->preserve_security_context && 0 <= dest_desc)
reset the context as per the default context, which has already been
set according to the src.
When using the mutually exclusive -Z option, then adjust the type of
the existing context according to the system default for the dest.
Note we set the context here, _after_ the file is opened, lest the
new context disallow that. */
if ((x->set_security_context || x->preserve_security_context)
&& 0 <= dest_desc)
{
bool all_errors = (!x->data_copy_required
|| x->require_preserve_context);
bool some_errors = !all_errors && !x->reduce_diagnostics;
security_context_t con = NULL;

if (getfscreatecon (&con) < 0)
if (! set_file_security_ctx (dst_name, x->preserve_security_context,
false, x))
{
if (all_errors || (some_errors && !errno_unsupported (errno)))
error (0, errno, _("failed to get file system create context"));
if (x->require_preserve_context)
{
return_val = false;
goto close_src_and_dst_desc;
}
}

if (con)
{
if (fsetfilecon (dest_desc, con) < 0)
{
if (all_errors || (some_errors && !errno_unsupported (errno)))
error (0, errno,
_("failed to set the security context of %s to %s"),
quote_n (0, dst_name), quote_n (1, con));
if (x->require_preserve_context)
{
return_val = false;
freecon (con);
goto close_src_and_dst_desc;
}
}
freecon (con);
}
}

if (dest_desc < 0 && x->unlink_dest_after_failed_open)
Expand All @@ -888,6 +973,18 @@ copy_reg (char const *src_name, char const *dst_name,

/* Tell caller that the destination file was unlinked. */
*new_dst = true;

/* Ensure there is no race where a file may be left without
an appropriate security context. */
if (x->set_security_context)
{
if (! set_process_security_ctx (src_name, dst_name, dst_mode,
*new_dst, x))
{
return_val = false;
goto close_src_desc;
}
}
}
}

Expand Down Expand Up @@ -2113,6 +2210,12 @@ copy_internal (char const *src_name, char const *dst_name,
emit_verbose (src_name, dst_name,
backup_succeeded ? dst_backup : NULL);

if (x->set_security_context)
{
/* -Z failures are only warnings currently. */
(void) set_file_security_ctx (dst_name, false, true, x);
}

if (rename_succeeded)
*rename_succeeded = true;

Expand Down Expand Up @@ -2222,40 +2325,12 @@ copy_internal (char const *src_name, char const *dst_name,

delayed_ok = true;

if (x->preserve_security_context)
{
bool all_errors = !x->data_copy_required || x->require_preserve_context;
bool some_errors = !all_errors && !x->reduce_diagnostics;
security_context_t con;

if (0 <= lgetfilecon (src_name, &con))
{
if (setfscreatecon (con) < 0)
{
if (all_errors || (some_errors && !errno_unsupported (errno)))
error (0, errno,
_("failed to set default file creation context to %s"),
quote (con));
if (x->require_preserve_context)
{
freecon (con);
return false;
}
}
freecon (con);
}
else
{
if (all_errors || (some_errors && !errno_unsupported (errno)))
{
error (0, errno,
_("failed to get security context of %s"),
quote (src_name));
}
if (x->require_preserve_context)
return false;
}
}
/* If required, set the default security context for new files.
Also for existing files this is used as a reference
when copying the context with --preserve=context.
FIXME: Do we need to consider dst_mode_bits here? */
if (! set_process_security_ctx (src_name, dst_name, src_mode, new_dst, x))
return false;

if (S_ISDIR (src_mode))
{
Expand Down Expand Up @@ -2521,6 +2596,19 @@ copy_internal (char const *src_name, char const *dst_name,
goto un_backup;
}

/* With -Z or --preserve=context, set the context for existing files.
Note this is done already for copy_reg() for reasons described therein. */
if (!new_dst && !x->copy_as_regular
&& (x->set_security_context || x->preserve_security_context))
{
if (! set_file_security_ctx (dst_name, x->preserve_security_context,
false, x))
{
if (x->require_preserve_context)
goto un_backup;
}
}

if (command_line_arg && x->dest_info)
{
/* Now that the destination file is very likely to exist,
Expand Down
3 changes: 3 additions & 0 deletions src/copy.h
Expand Up @@ -159,6 +159,9 @@ struct cp_options
bool preserve_timestamps;
bool explicit_no_preserve_mode;

/* If true, attempt to set specified security context */
bool set_security_context;

/* Enabled for mv, and for cp by the --preserve=links option.
If true, attempt to preserve in the destination files any
logical hard links between the source files. If used with cp's
Expand Down
64 changes: 53 additions & 11 deletions src/cp.c
Expand Up @@ -141,6 +141,7 @@ static struct option const long_opts[] =
{"target-directory", required_argument, NULL, 't'},
{"update", no_argument, NULL, 'u'},
{"verbose", no_argument, NULL, 'v'},
{GETOPT_SELINUX_CONTEXT_OPTION_DECL},
{GETOPT_HELP_OPTION_DECL},
{GETOPT_VERSION_OPTION_DECL},
{NULL, 0, NULL, 0}
Expand Down Expand Up @@ -227,6 +228,10 @@ Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.\n\
destination file is missing\n\
-v, --verbose explain what is being done\n\
-x, --one-file-system stay on this file system\n\
"), stdout);
fputs (_("\
-Z, --context[=CTX] set SELinux security context of destination\n\
file to default type, or to CTX if specified\n\
"), stdout);
fputs (HELP_OPTION_DESCRIPTION, stdout);
fputs (VERSION_OPTION_DESCRIPTION, stdout);
Expand Down Expand Up @@ -782,8 +787,9 @@ cp_option_init (struct cp_options *x)
x->preserve_mode = false;
x->preserve_timestamps = false;
x->explicit_no_preserve_mode = false;
x->preserve_security_context = false;
x->require_preserve_context = false;
x->preserve_security_context = false; /* -a or --preserve=context. */
x->require_preserve_context = false; /* --preserve=context. */
x->set_security_context = false; /* -Z, set sys default context. */
x->preserve_xattr = false;
x->reduce_diagnostics = false;
x->require_preserve_xattr = false;
Expand Down Expand Up @@ -876,8 +882,8 @@ decode_preserve_arg (char const *arg, struct cp_options *x, bool on_off)
break;

case PRESERVE_CONTEXT:
x->preserve_security_context = on_off;
x->require_preserve_context = on_off;
x->preserve_security_context = on_off;
break;

case PRESERVE_XATTR:
Expand Down Expand Up @@ -918,6 +924,7 @@ main (int argc, char **argv)
bool copy_contents = false;
char *target_directory = NULL;
bool no_target_directory = false;
security_context_t scontext = NULL;

initialize_main (&argc, &argv);
set_program_name (argv[0]);
Expand All @@ -934,7 +941,7 @@ main (int argc, char **argv)
we'll actually use backup_suffix_string. */
backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX");

while ((c = getopt_long (argc, argv, "abdfHilLnprst:uvxPRS:T",
while ((c = getopt_long (argc, argv, "abdfHilLnprst:uvxPRS:TZ",
long_opts, NULL))
!= -1)
{
Expand Down Expand Up @@ -1092,6 +1099,24 @@ main (int argc, char **argv)
x.one_file_system = true;
break;


case 'Z':
/* politely decline if we're not on a selinux-enabled kernel. */
if (selinux_enabled)
{
if (optarg)
scontext = optarg;
else
x.set_security_context = true;
}
else if (optarg)
{
error (0, 0,
_("warning: ignoring --context; "
"it requires an SELinux-enabled kernel"));
}
break;

case 'S':
make_backups = true;
backup_suffix_string = optarg;
Expand Down Expand Up @@ -1150,13 +1175,30 @@ main (int argc, char **argv)
if (x.unlink_dest_after_failed_open && (x.hard_link || x.symbolic_link))
x.unlink_dest_before_opening = true;

if (x.preserve_security_context)
{
if (!selinux_enabled)
error (EXIT_FAILURE, 0,
_("cannot preserve security context "
"without an SELinux-enabled kernel"));
}
/* Ensure -Z overrides -a. */
if ((x.set_security_context || scontext)
&& ! x.require_preserve_context)
x.preserve_security_context = false;

if (x.preserve_security_context && (x.set_security_context || scontext))
error (EXIT_FAILURE, 0,
_("cannot set target context and preserve it"));

if (x.require_preserve_context && ! selinux_enabled)
error (EXIT_FAILURE, 0,
_("cannot preserve security context "
"without an SELinux-enabled kernel"));

/* FIXME: This handles new files. But what about existing files?
I.E. if updating a tree, new files would have the specified context,
but shouldn't existing files be updated for consistency like this?
if (scontext)
restorecon (dst_path, 0, true);
*/
if (scontext && setfscreatecon (optarg) < 0)
error (EXIT_FAILURE, errno,
_("failed to set default file creation context to %s"),
quote (optarg));

#if !USE_XATTR
if (x.require_preserve_xattr)
Expand Down
6 changes: 3 additions & 3 deletions src/id.c
Expand Up @@ -40,8 +40,8 @@
proper_name ("Arnold Robbins"), \
proper_name ("David MacKenzie")

/* If nonzero, output only the SELinux context. -Z */
static int just_context = 0;
/* If nonzero, output only the SELinux context. */
static bool just_context = 0;

static void print_user (uid_t uid);
static void print_full_info (const char *username);
Expand Down Expand Up @@ -155,7 +155,7 @@ main (int argc, char **argv)
error (EXIT_FAILURE, 0,
_("--context (-Z) works only on an SELinux-enabled kernel"));
#endif
just_context = 1;
just_context = true;
break;

case 'g':
Expand Down
53 changes: 34 additions & 19 deletions src/install.c
Expand Up @@ -279,7 +279,6 @@ cp_option_init (struct cp_options *x)
x->reduce_diagnostics=false;
x->data_copy_required = true;
x->require_preserve = false;
x->require_preserve_context = false;
x->require_preserve_xattr = false;
x->recursive = false;
x->sparse_mode = SPARSE_AUTO;
Expand All @@ -295,7 +294,9 @@ cp_option_init (struct cp_options *x)

x->open_dangling_dest_symlink = false;
x->update = false;
x->preserve_security_context = false;
x->require_preserve_context = false; /* Not used by install currently. */
x->preserve_security_context = false; /* Whether to copy context from src. */
x->set_security_context = false; /* Whether to set sys default context. */
x->preserve_xattr = false;
x->verbose = false;
x->dest_info = NULL;
Expand All @@ -305,7 +306,8 @@ cp_option_init (struct cp_options *x)
#ifdef ENABLE_MATCHPATHCON
/* Modify file context to match the specified policy.
If an error occurs the file will remain with the default directory
context. */
context. Note this sets the context to that returned by matchpathcon,
and thus discards MLS levels and user identity of the FILE. */
static void
setdefaultfilecon (char const *file)
{
Expand Down Expand Up @@ -359,7 +361,8 @@ setdefaultfilecon (char const *file)
first_call = false;

/* If there's an error determining the context, or it has none,
return to allow default context */
return to allow default context. Note the "<<none>>" check
is only needed for libselinux < 1.20 (2005-01-04). */
if ((matchpathcon (file, st.st_mode, &scontext) != 0)
|| STREQ (scontext, "<<none>>"))
{
Expand Down Expand Up @@ -644,8 +647,8 @@ In the 4th form, create all components of the given DIRECTORY(ies).\n\
"), stdout);
fputs (_("\
--preserve-context preserve SELinux security context\n\
-Z, --context=CONTEXT set SELinux security context of files and directories\
\n\
-Z, --context[=CTX] set SELinux security context of destination file to\n\
default type, or to CTX if specified\n\
"), stdout);

fputs (HELP_OPTION_DESCRIPTION, stdout);
Expand Down Expand Up @@ -791,7 +794,7 @@ main (int argc, char **argv)
we'll actually use backup_suffix_string. */
backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX");

while ((optc = getopt_long (argc, argv, "bcCsDdg:m:o:pt:TvS:Z:", long_options,
while ((optc = getopt_long (argc, argv, "bcCsDdg:m:o:pt:TvS:Z", long_options,
NULL)) != -1)
{
switch (optc)
Expand Down Expand Up @@ -863,7 +866,7 @@ main (int argc, char **argv)
break;

case PRESERVE_CONTEXT_OPTION:
if ( ! selinux_enabled)
if (! selinux_enabled)
{
error (0, 0, _("WARNING: ignoring --preserve-context; "
"this kernel is not SELinux-enabled"));
Expand All @@ -873,14 +876,27 @@ main (int argc, char **argv)
use_default_selinux_context = false;
break;
case 'Z':
if ( ! selinux_enabled)
if (selinux_enabled)
{
error (0, 0, _("WARNING: ignoring --context (-Z); "
"this kernel is not SELinux-enabled"));
break;
/* Disable use of the install(1) specific setdefaultfilecon().
Note setdefaultfilecon() is different from the newer and more
generic restorecon() in that the former sets the context of
the dest files to that returned by matchpathcon directly,
thus discarding MLS level and user identity of the file.
TODO: consider removing setdefaultfilecon() in future. */
use_default_selinux_context = false;

if (optarg)
scontext = optarg;
else
x.set_security_context = true;
}
else if (optarg)
{
error (0, 0,
_("warning: ignoring --context; "
"it requires an SELinux-enabled kernel"));
}
scontext = optarg;
use_default_selinux_context = false;
break;
case_GETOPT_HELP_CHAR;
case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
Expand All @@ -897,11 +913,6 @@ main (int argc, char **argv)
error (EXIT_FAILURE, 0,
_("target directory not allowed when installing a directory"));

if (x.preserve_security_context && scontext != NULL)
error (EXIT_FAILURE, 0,
_("cannot force target context to %s and preserve it"),
quote (scontext));

if (backup_suffix_string)
simple_backup_suffix = xstrdup (backup_suffix_string);

Expand All @@ -910,6 +921,10 @@ main (int argc, char **argv)
version_control_string)
: no_backups);

if (x.preserve_security_context && (x.set_security_context || scontext))
error (EXIT_FAILURE, 0,
_("cannot set target context and preserve it"));

if (scontext && setfscreatecon (scontext) < 0)
error (EXIT_FAILURE, errno,
_("failed to set default file creation context to %s"),
Expand Down
16 changes: 12 additions & 4 deletions src/local.mk
Expand Up @@ -312,6 +312,10 @@ RELEASE_YEAR = \
`sed -n '/.*COPYRIGHT_YEAR = \([0-9][0-9][0-9][0-9]\) };/s//\1/p' \
$(top_srcdir)/lib/version-etc.c`

selinux_sources = \
src/selinux.c \
src/selinux.h

copy_sources = \
src/copy.c \
src/cp-hash.c \
Expand All @@ -323,12 +327,13 @@ copy_sources = \
# to install before applying any user-specified name transformations.

transform = s/ginstall/install/; $(program_transform_name)
src_ginstall_SOURCES = src/install.c src/prog-fprintf.c $(copy_sources)
src_ginstall_SOURCES = src/install.c src/prog-fprintf.c $(copy_sources) \
$(selinux_sources)

# This is for the '[' program. Automake transliterates '[' and '/' to '_'.
src___SOURCES = src/lbracket.c

src_cp_SOURCES = src/cp.c $(copy_sources)
src_cp_SOURCES = src/cp.c $(copy_sources) $(selinux_sources)
src_dir_SOURCES = src/ls.c src/ls-dir.c
src_vdir_SOURCES = src/ls.c src/ls-vdir.c
src_id_SOURCES = src/id.c src/group-list.c
Expand All @@ -341,12 +346,15 @@ src_kill_SOURCES = src/kill.c src/operand2sig.c
src_realpath_SOURCES = src/realpath.c src/relpath.c src/relpath.h
src_timeout_SOURCES = src/timeout.c src/operand2sig.c

src_mv_SOURCES = src/mv.c src/remove.c $(copy_sources)
src_mv_SOURCES = src/mv.c src/remove.c $(copy_sources) $(selinux_sources)
src_rm_SOURCES = src/rm.c src/remove.c

src_mkdir_SOURCES = src/mkdir.c src/prog-fprintf.c
src_mkdir_SOURCES = src/mkdir.c src/prog-fprintf.c $(selinux_sources)
src_rmdir_SOURCES = src/rmdir.c src/prog-fprintf.c

src_mkfifo_SOURCES = src/mkfifo.c $(selinux_sources)
src_mknod_SOURCES = src/mknod.c $(selinux_sources)

src_df_SOURCES = src/df.c src/find-mount-point.c
src_stat_SOURCES = src/stat.c src/find-mount-point.c

Expand Down
86 changes: 75 additions & 11 deletions src/mkdir.c
Expand Up @@ -29,6 +29,7 @@
#include "prog-fprintf.h"
#include "quote.h"
#include "savewd.h"
#include "selinux.h"
#include "smack.h"

/* The official name of this program (e.g., no 'g' prefix). */
Expand Down Expand Up @@ -65,8 +66,8 @@ Create the DIRECTORY(ies), if they do not already exist.\n\
-m, --mode=MODE set file mode (as in chmod), not a=rwx - umask\n\
-p, --parents no error if existing, make parent directories as needed\n\
-v, --verbose print a message for each created directory\n\
-Z, --context=CTX set the SELinux security context of each created\n\
directory to CTX\n\
-Z, --context[=CTX] set the SELinux security context of each created\n\
directory to default type or to CTX if specified\n\
"), stdout);
fputs (HELP_OPTION_DESCRIPTION, stdout);
fputs (VERSION_OPTION_DESCRIPTION, stdout);
Expand All @@ -91,6 +92,9 @@ struct mkdir_options
/* File mode bits affected by MODE. */
mode_t mode_bits;

/* Set the SELinux File Context. */
bool set_security_context;

/* If not null, format to use when reporting newly made directories. */
char const *created_directory_format;
};
Expand All @@ -113,12 +117,16 @@ static int
make_ancestor (char const *dir, char const *component, void *options)
{
struct mkdir_options const *o = options;
int r;

if (o->set_security_context && defaultcon (dir, S_IFDIR) < 0)
error (0, errno, _("failed to set default creation context for %s"),
quote (dir));

mode_t user_wx = S_IWUSR | S_IXUSR;
bool self_denying_umask = (o->umask_value & user_wx) != 0;
if (self_denying_umask)
umask (o->umask_value & ~user_wx);
r = mkdir (component, S_IRWXUGO);
int r = mkdir (component, S_IRWXUGO);
if (self_denying_umask)
{
int mkdir_errno = errno;
Expand All @@ -138,11 +146,46 @@ static int
process_dir (char *dir, struct savewd *wd, void *options)
{
struct mkdir_options const *o = options;
return (make_dir_parents (dir, wd, o->make_ancestor_function, options,
o->mode, announce_mkdir,
o->mode_bits, (uid_t) -1, (gid_t) -1, true)
? EXIT_SUCCESS
: EXIT_FAILURE);
bool set_defaultcon = false;

/* If possible set context before DIR created. */
if (o->set_security_context)
{
if (! o->make_ancestor_function)
set_defaultcon = true;
else
{
char *pdir = dir_name (dir);
struct stat st;
if (STREQ (pdir, ".")
|| (stat (pdir, &st) == 0 && S_ISDIR (st.st_mode)))
set_defaultcon = true;
free (pdir);
}
if (set_defaultcon && defaultcon (dir, S_IFDIR) < 0)
error (0, errno, _("failed to set default creation context for %s"),
quote (dir));
}

int ret = (make_dir_parents (dir, wd, o->make_ancestor_function, options,
o->mode, announce_mkdir,
o->mode_bits, (uid_t) -1, (gid_t) -1, true)
? EXIT_SUCCESS
: EXIT_FAILURE);

/* FIXME: Due to the current structure of make_dir_parents()
we don't have the facility to call defaultcon() before the
final component of DIR is created. So for now, create the
final component with the context from previous component
and here we set the context for the final component. */
if (ret == EXIT_SUCCESS && o->set_security_context && ! set_defaultcon)
{
if (restorecon (last_component (dir), false, false) < 0)
error (0, errno, _("failed to set restore context for %s"),
quote (dir));
}

return ret;
}

int
Expand All @@ -157,6 +200,7 @@ main (int argc, char **argv)
options.mode = S_IRWXUGO;
options.mode_bits = 0;
options.created_directory_format = NULL;
options.set_security_context = false;

initialize_main (&argc, &argv);
set_program_name (argv[0]);
Expand All @@ -166,7 +210,7 @@ main (int argc, char **argv)

atexit (close_stdout);

while ((optc = getopt_long (argc, argv, "pm:vZ:", longopts, NULL)) != -1)
while ((optc = getopt_long (argc, argv, "pm:vZ", longopts, NULL)) != -1)
{
switch (optc)
{
Expand All @@ -180,7 +224,24 @@ main (int argc, char **argv)
options.created_directory_format = _("created directory %s");
break;
case 'Z':
scontext = optarg;
if (is_smack_enabled ())
{
/* We don't yet support -Z to restore context with SMACK. */
scontext = optarg;
}
else if (is_selinux_enabled () > 0)
{
if (optarg)
scontext = optarg;
else
options.set_security_context = true;
}
else if (optarg)
{
error (0, 0,
_("warning: ignoring --context; "
"it requires an SELinux/SMACK-enabled kernel"));
}
break;
case_GETOPT_HELP_CHAR;
case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
Expand All @@ -195,6 +256,9 @@ main (int argc, char **argv)
usage (EXIT_FAILURE);
}

/* FIXME: This assumes mkdir() is done in the same process.
If that's not always the case we would need to call this
like we do when options.set_security_context == true. */
if (scontext)
{
int ret = 0;
Expand Down
52 changes: 38 additions & 14 deletions src/mkfifo.c
Expand Up @@ -26,6 +26,7 @@
#include "error.h"
#include "modechange.h"
#include "quote.h"
#include "selinux.h"
#include "smack.h"

/* The official name of this program (e.g., no 'g' prefix). */
Expand Down Expand Up @@ -60,7 +61,8 @@ Create named pipes (FIFOs) with the given NAMEs.\n\
-m, --mode=MODE set file permission bits to MODE, not a=rw - umask\n\
"), stdout);
fputs (_("\
-Z, --context=CTX set the SELinux security context of each NAME to CTX\n\
-Z, --context[=CTX] set the SELinux security context of each NAME to\n\
default type, or CTX if specified\n\
"), stdout);
fputs (HELP_OPTION_DESCRIPTION, stdout);
fputs (VERSION_OPTION_DESCRIPTION, stdout);
Expand All @@ -77,6 +79,7 @@ main (int argc, char **argv)
int exit_status = EXIT_SUCCESS;
int optc;
security_context_t scontext = NULL;
bool set_security_context = false;

initialize_main (&argc, &argv);
set_program_name (argv[0]);
Expand All @@ -86,15 +89,32 @@ main (int argc, char **argv)

atexit (close_stdout);

while ((optc = getopt_long (argc, argv, "m:Z:", longopts, NULL)) != -1)
while ((optc = getopt_long (argc, argv, "m:Z", longopts, NULL)) != -1)
{
switch (optc)
{
case 'm':
specified_mode = optarg;
break;
case 'Z':
scontext = optarg;
if (is_smack_enabled ())
{
/* We don't yet support -Z to restore context with SMACK. */
scontext = optarg;
}
else if (is_selinux_enabled () > 0)
{
if (optarg)
scontext = optarg;
else
set_security_context = true;
}
else if (optarg)
{
error (0, 0,
_("warning: ignoring --context; "
"it requires an SELinux/SMACK-enabled kernel"));
}
break;
case_GETOPT_HELP_CHAR;
case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
Expand Down Expand Up @@ -140,17 +160,21 @@ main (int argc, char **argv)
}

for (; optind < argc; ++optind)
if (mkfifo (argv[optind], newmode) != 0)
{
error (0, errno, _("cannot create fifo %s"), quote (argv[optind]));
exit_status = EXIT_FAILURE;
}
else if (specified_mode && lchmod (argv[optind], newmode) != 0)
{
error (0, errno, _("cannot set permissions of `%s'"),
quote (argv[optind]));
exit_status = EXIT_FAILURE;
}
{
if (set_security_context)
defaultcon (argv[optind], S_IFIFO);
if (mkfifo (argv[optind], newmode) != 0)
{
error (0, errno, _("cannot create fifo %s"), quote (argv[optind]));
exit_status = EXIT_FAILURE;
}
else if (specified_mode && lchmod (argv[optind], newmode) != 0)
{
error (0, errno, _("cannot set permissions of `%s'"),
quote (argv[optind]));
exit_status = EXIT_FAILURE;
}
}

exit (exit_status);
}
31 changes: 28 additions & 3 deletions src/mknod.c
Expand Up @@ -26,6 +26,7 @@
#include "error.h"
#include "modechange.h"
#include "quote.h"
#include "selinux.h"
#include "smack.h"
#include "xstrtol.h"

Expand Down Expand Up @@ -62,7 +63,8 @@ Create the special file NAME of the given TYPE.\n\
-m, --mode=MODE set file permission bits to MODE, not a=rw - umask\n\
"), stdout);
fputs (_("\
-Z, --context=CTX set the SELinux security context of NAME to CTX\n\
-Z, --context[=CTX] set the SELinux security context of NAME to\n\
default type, or to CTX if specified\n\
"), stdout);
fputs (HELP_OPTION_DESCRIPTION, stdout);
fputs (VERSION_OPTION_DESCRIPTION, stdout);
Expand Down Expand Up @@ -94,6 +96,7 @@ main (int argc, char **argv)
int expected_operands;
mode_t node_type;
security_context_t scontext = NULL;
bool set_security_context = false;

initialize_main (&argc, &argv);
set_program_name (argv[0]);
Expand All @@ -103,15 +106,32 @@ main (int argc, char **argv)

atexit (close_stdout);

while ((optc = getopt_long (argc, argv, "m:Z:", longopts, NULL)) != -1)
while ((optc = getopt_long (argc, argv, "m:Z", longopts, NULL)) != -1)
{
switch (optc)
{
case 'm':
specified_mode = optarg;
break;
case 'Z':
scontext = optarg;
if (is_smack_enabled ())
{
/* We don't yet support -Z to restore context with SMACK. */
scontext = optarg;
}
else if (is_selinux_enabled () > 0)
{
if (optarg)
scontext = optarg;
else
set_security_context = true;
}
else if (optarg)
{
error (0, 0,
_("warning: ignoring --context; "
"it requires an SELinux/SMACK-enabled kernel"));
}
break;
case_GETOPT_HELP_CHAR;
case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
Expand Down Expand Up @@ -224,12 +244,17 @@ main (int argc, char **argv)
error (EXIT_FAILURE, 0, _("invalid device %s %s"), s_major, s_minor);
#endif

if (set_security_context)
defaultcon (argv[optind], node_type);

if (mknod (argv[optind], newmode | node_type, device) != 0)
error (EXIT_FAILURE, errno, "%s", quote (argv[optind]));
}
break;

case 'p': /* 'pipe' */
if (set_security_context)
defaultcon (argv[optind], S_IFIFO);
if (mkfifo (argv[optind], newmode) != 0)
error (EXIT_FAILURE, errno, "%s", quote (argv[optind]));
break;
Expand Down
15 changes: 14 additions & 1 deletion src/mv.c
Expand Up @@ -55,6 +55,7 @@ static bool remove_trailing_slashes;
static struct option const long_options[] =
{
{"backup", optional_argument, NULL, 'b'},
{"context", no_argument, NULL, 'Z'},
{"force", no_argument, NULL, 'f'},
{"interactive", no_argument, NULL, 'i'},
{"no-clobber", no_argument, NULL, 'n'},
Expand Down Expand Up @@ -120,6 +121,7 @@ cp_option_init (struct cp_options *x)
x->preserve_timestamps = true;
x->explicit_no_preserve_mode= false;
x->preserve_security_context = selinux_enabled;
x->set_security_context = false;
x->reduce_diagnostics = false;
x->data_copy_required = true;
x->require_preserve = false; /* FIXME: maybe make this an option */
Expand Down Expand Up @@ -316,6 +318,8 @@ If you specify more than one of -i, -f, -n, only the final one takes effect.\n\
than the destination file or when the\n\
destination file is missing\n\
-v, --verbose explain what is being done\n\
-Z, --context set SELinux security context of destination\n\
file to default type\n\
"), stdout);
fputs (HELP_OPTION_DESCRIPTION, stdout);
fputs (VERSION_OPTION_DESCRIPTION, stdout);
Expand Down Expand Up @@ -350,6 +354,7 @@ main (int argc, char **argv)
bool no_target_directory = false;
int n_files;
char **file;
bool selinux_enabled = (0 < is_selinux_enabled ());

initialize_main (&argc, &argv);
set_program_name (argv[0]);
Expand All @@ -368,7 +373,7 @@ main (int argc, char **argv)
we'll actually use backup_suffix_string. */
backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX");

while ((c = getopt_long (argc, argv, "bfint:uvS:T", long_options, NULL))
while ((c = getopt_long (argc, argv, "bfint:uvS:TZ", long_options, NULL))
!= -1)
{
switch (c)
Expand Down Expand Up @@ -418,6 +423,14 @@ main (int argc, char **argv)
make_backups = true;
backup_suffix_string = optarg;
break;
case 'Z':
/* politely decline if we're not on a selinux-enabled kernel. */
if (selinux_enabled)
{
x.preserve_security_context = false;
x.set_security_context = true;
}
break;
case_GETOPT_HELP_CHAR;
case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
default:
Expand Down
9 changes: 4 additions & 5 deletions src/runcon.c
Expand Up @@ -85,7 +85,7 @@ Usage: %s CONTEXT COMMAND [args]\n\
or: %s [ -c ] [-u USER] [-r ROLE] [-t TYPE] [-l RANGE] COMMAND [args]\n\
"), program_name, program_name);
fputs (_("\
Run a program in a different security context.\n\
Run a program in a different SELinux security context.\n\
With neither CONTEXT nor COMMAND, print the current security context.\n\
"), stdout);

Expand Down Expand Up @@ -197,8 +197,8 @@ main (int argc, char **argv)
}

if (is_selinux_enabled () != 1)
error (EXIT_FAILURE, 0,
_("%s may be used only on a SELinux kernel"), program_name);
error (EXIT_FAILURE, 0, _("%s may be used only on a SELinux kernel"),
program_name);

if (context)
{
Expand All @@ -223,8 +223,7 @@ main (int argc, char **argv)
/* compute result of process transition */
if (security_compute_create (cur_context, file_context,
SECCLASS_PROCESS, &new_context) != 0)
error (EXIT_FAILURE, errno,
_("failed to compute a new context"));
error (EXIT_FAILURE, errno, _("failed to compute a new context"));
/* free contexts */
freecon (file_context);
freecon (cur_context);
Expand Down
331 changes: 331 additions & 0 deletions src/selinux.c
@@ -0,0 +1,331 @@
/* selinux - core functions for maintaining SELinux labeling
Copyright (C) 2012 Red Hat, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */

/* Written by Daniel Walsh <dwalsh@redhat.com> */

#include <config.h>
#include <selinux/selinux.h>
#include <selinux/flask.h>
#include <selinux/context.h>
#include <sys/types.h>

#include "error.h"
#include "system.h"
#include "canonicalize.h"
#include "dosname.h"
#include "fts.h"
#include "quote.h"
#include "selinux.h"

/*
This function has being added to libselinux-2.1.12-5, but is here
for support with older versions of SELinux
Translates a mode into an Internal SELinux security_class definition.
Returns 0 on failure, with errno set to EINVAL.
*/
static security_class_t
mode_to_security_class (mode_t m)
{

if (S_ISREG (m))
return string_to_security_class ("file");
if (S_ISDIR (m))
return string_to_security_class ("dir");
if (S_ISCHR (m))
return string_to_security_class ("chr_file");
if (S_ISBLK (m))
return string_to_security_class ("blk_file");
if (S_ISFIFO (m))
return string_to_security_class ("fifo_file");
if (S_ISLNK (m))
return string_to_security_class ("lnk_file");
if (S_ISSOCK (m))
return string_to_security_class ("sock_file");

errno = EINVAL;
return 0;
}

/*
This function takes a PATH and a MODE and then asks SELinux what the label
of the path object would be if the current process label created it.
It then returns the label.
Returns -1 on failure. errno will be set appropriately.
*/

static int
computecon (char const *path, mode_t mode, security_context_t * con)
{
security_context_t scon = NULL;
security_context_t tcon = NULL;
security_class_t tclass;
int rc = -1;

char *dir = dir_name (path);
if (!dir)
goto quit;
if (getcon (&scon) < 0)
goto quit;
if (getfilecon (dir, &tcon) < 0)
goto quit;
tclass = mode_to_security_class (mode);
if (!tclass)
goto quit;
rc = security_compute_create (scon, tcon, tclass, con);

quit:
free (dir);
freecon (scon);
freecon (tcon);
return rc;
}

/*
This function takes a path and a mode, it calls computecon to get the
label of the path object if the current process created it, then it calls
matchpathcon to get the default type for the object. It substitutes the
default type into label. It tells the SELinux Kernel to label all new file
system objects created by the current process with this label.
Returns -1 on failure. errno will be set appropriately.
*/
int
defaultcon (char const *path, mode_t mode)
{
int rc = -1;
security_context_t scon = NULL, tcon = NULL;
context_t scontext = NULL, tcontext = NULL;
const char *contype;
char *constr;
char *newpath = NULL;

if (! IS_ABSOLUTE_FILE_NAME (path))
{
/* Generate absolute path as required by subsequent matchpathcon(),
with libselinux < 2.1.5 2011-0826. */
newpath = canonicalize_filename_mode (path, CAN_MISSING);
if (! newpath)
error (EXIT_FAILURE, errno, _("error canonicalizing %s"),
quote (path));
path = newpath;
}

if (matchpathcon (path, mode, &scon) < 0)
{
/* "No such file or directory" is a confusing error,
when processing files, when in fact it was the
associated default context that was not found.
Therefore map the error to something more appropriate
to the context in which we're using matchpathcon(). */
if (errno == ENOENT)
errno = ENODATA;
goto quit;
}
if (computecon (path, mode, &tcon) < 0)
goto quit;
if (!(scontext = context_new (scon)))
goto quit;
if (!(tcontext = context_new (tcon)))
goto quit;

if (!(contype = context_type_get (scontext)))
goto quit;
if (context_type_set (tcontext, contype))
goto quit;
if (!(constr = context_str (tcontext)))
goto quit;

rc = setfscreatecon (constr);

quit:
context_free (scontext);
context_free (tcontext);
freecon (scon);
freecon (tcon);
free (newpath);
return rc;
}

/*
This function takes a PATH of an existing file system object, and a LOCAL
boolean that indicates whether the function should set the object's label
to the default for the local process, or one using system wide settings.
If LOCAL == true, it will ask the SELinux Kernel what the default label
for all objects created should be and then sets the label on the object.
Otherwise it calls matchpathcon on the object to ask the system what the
default label should be, extracts the type field and then modifies the file
system object. Note only the type field is updated, thus preserving MLS
levels and user identity etc. of the PATH.
Returns -1 on failure. errno will be set appropriately.
*/
static int
restorecon_private (char const *path, bool local)
{
int rc = -1;
struct stat sb;
security_context_t scon = NULL, tcon = NULL;
context_t scontext = NULL, tcontext = NULL;
const char *contype;
char *constr;
int fd;

if (local)
{
if (getfscreatecon (&tcon) < 0)
return rc;
rc = lsetfilecon (path, tcon);
freecon (tcon);
return rc;
}

fd = open (path, O_RDONLY | O_NOFOLLOW);
if (fd == -1 && (errno != ELOOP))
goto quit;

if (fd != -1)
{
if (fstat (fd, &sb) < 0)
goto quit;
}
else
{
if (lstat (path, &sb) < 0)
goto quit;
}

if (matchpathcon (path, sb.st_mode, &scon) < 0)
{
/* "No such file or directory" is a confusing error,
when processing files, when in fact it was the
associated default context that was not found.
Therefore map the error to something more appropriate
to the context in which we're using matchpathcon(). */
if (errno == ENOENT)
errno = ENODATA;
goto quit;
}
if (!(scontext = context_new (scon)))
goto quit;

if (fd != -1)
{
if (fgetfilecon (fd, &tcon) < 0)
goto quit;
}
else
{
if (lgetfilecon (path, &tcon) < 0)
goto quit;
}

if (!(tcontext = context_new (tcon)))
goto quit;

if (!(contype = context_type_get (scontext)))
goto quit;
if (context_type_set (tcontext, contype))
goto quit;
if (!(constr = context_str (tcontext)))
goto quit;

if (fd != -1)
rc = fsetfilecon (fd, constr);
else
rc = lsetfilecon (path, constr);

quit:
if (fd != -1)
close (fd);
context_free (scontext);
context_free (tcontext);
freecon (scon);
freecon (tcon);
return rc;
}

/*
This function takes three parameters:
PATH of an existing file system object.
A RECURSE boolean which if the file system object is a directory, will
call restorecon_private on every file system object in the directory.
A LOCAL boolean that indicates whether the function should set object labels
to the default for the local process, or use system wide settings.
Returns false on failure. errno will be set appropriately.
*/
bool
restorecon (char const *path, bool recurse, bool local)
{
char *newpath = NULL;
FTS *fts;
bool ok = true;

if (! IS_ABSOLUTE_FILE_NAME (path) && ! local)
{
/* Generate absolute path as required by subsequent matchpathcon(),
with libselinux < 2.1.5 2011-0826. Also generating the absolute
path before the fts walk, will generate absolute paths in the
fts entries, which may be quicker to process in any case. */
newpath = canonicalize_filename_mode (path, CAN_MISSING);
if (! newpath)
error (EXIT_FAILURE, errno, _("error canonicalizing %s"),
quote (path));
}

const char *ftspath[2] = { newpath ? newpath : path, NULL };

if (! recurse)
{
ok = restorecon_private (*ftspath, local) != -1;
free (newpath);
return ok;
}

fts = fts_open ((char *const *) ftspath, FTS_PHYSICAL, NULL);
while (1)
{
FTSENT *ent;

ent = fts_read (fts);
if (ent == NULL)
{
if (errno != 0)
{
/* FIXME: try to give a better message */
error (0, errno, _("fts_read failed"));
ok = false;
}
break;
}

ok &= restorecon_private (fts->fts_path, local) != -1;
}

if (fts_close (fts) != 0)
{
error (0, errno, _("fts_close failed"));
ok = false;
}

free (newpath);
return ok;
}
25 changes: 25 additions & 0 deletions src/selinux.h
@@ -0,0 +1,25 @@
/* selinux - core functions for maintaining SELinux labelking
Copyright (C) 2012 Red Hat, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */

/* Written by Daniel Walsh <dwalsh@redhat.com> */

#ifndef COREUTILS_SELINUX_H
# define COREUTILS_SELINUX_H

extern bool restorecon (char const *path, bool recurse, bool preserve);
extern int defaultcon (char const *path, mode_t mode);

#endif
2 changes: 1 addition & 1 deletion src/system.h
Expand Up @@ -330,7 +330,7 @@ enum
#define GETOPT_VERSION_OPTION_DECL \
"version", no_argument, NULL, GETOPT_VERSION_CHAR
#define GETOPT_SELINUX_CONTEXT_OPTION_DECL \
"context", required_argument, NULL, 'Z'
"context", optional_argument, NULL, 'Z'

#define case_GETOPT_HELP_CHAR \
case GETOPT_HELP_CHAR: \
Expand Down
76 changes: 73 additions & 3 deletions tests/cp/cp-a-selinux.sh
@@ -1,5 +1,5 @@
#!/bin/sh
# Ensure that cp -a and cp --preserve=context work properly.
# Ensure that cp -Z, -a and cp --preserve=context work properly.
# In particular, test on a writable NFS partition.
# Check also locally if --preserve=context, -a and --preserve=all
# does work
Expand Down Expand Up @@ -41,6 +41,45 @@ test -s err && fail=1 #there must be no stderr output for -a
ls -Z e | grep $ctx || fail=1
ls -Z f | grep $ctx || fail=1

# Check restorecon (-Z) functionality for file and directory
get_selinux_type() { ls -Zd "$1" | sed -n 's/.*:\(.*_t\):.*/\1/p'; }
# Also make a dir with our known context
mkdir c_d || framework_failure_
chcon $ctx c_d || framework_failure_
# Get the type of this known context for file and dir
old_type_f=$(get_selinux_type c)
old_type_d=$(get_selinux_type c_d)
# Setup copies for manipulation with restorecon
# and get the adjusted type for comparison
cp -a c Z1 || fail=1
cp -a c_d Z1_d || fail=1
if restorecon Z1 Z1_d 2>/dev/null; then
new_type_f=$(get_selinux_type Z1)
new_type_d=$(get_selinux_type Z1_d)

# Ensure -Z sets the type like restorecon does
cp -Z c Z2 || fail=1
cpZ_type_f=$(get_selinux_type Z2)
test "$cpZ_type_f" = "$new_type_f" || fail=1

# Ensuze -Z overrides -a and that dirs are handled too
cp -aZ c Z3 || fail=1
cp -aZ c_d Z3_d || fail=1
cpaZ_type_f=$(get_selinux_type Z3)
cpaZ_type_d=$(get_selinux_type Z3_d)
test "$cpaZ_type_f" = "$new_type_f" || fail=1
test "$cpaZ_type_d" = "$new_type_d" || fail=1

# Ensure -Z sets the type for existing files
mkdir -p existing/c_d || framework_failure_
touch existing/c || framework_failure_
cp -aZ c c_d existing || fail=1
cpaZ_type_f=$(get_selinux_type existing/c)
cpaZ_type_d=$(get_selinux_type existing/c_d)
test "$cpaZ_type_f" = "$new_type_f" || fail=1
test "$cpaZ_type_d" = "$new_type_d" || fail=1
fi

skip=0
# Create a file system, then mount it with the context=... option.
dd if=/dev/zero of=blob bs=8192 count=200 || skip=1
Expand Down Expand Up @@ -97,7 +136,7 @@ echo > g
cp --preserve=context f g 2> out && fail=1
# Here, we *do* expect the destination to be empty.
test -s g && fail=1
sed "s/ .g' to .*//" out > k
sed "s/ .g'.*//" out > k
mv k out
compare exp out || fail=1

Expand All @@ -107,8 +146,39 @@ echo > g
cp -a --preserve=context f g 2> out2 && fail=1
# Here, we *do* expect the destination to be empty.
test -s g && fail=1
sed "s/ .g' to .*//" out2 > k
sed "s/ .g'.*//" out2 > k
mv k out2
compare exp out2 || fail=1

for no_g_cmd in '' 'rm -f g'; do
# restorecon equivalent. Note even though the context
# returned from matchpathcon() will not match $ctx
# the resulting ENOTSUP warning will be suppressed.
# With absolute path
$no_g_cmd
cp -Z f $(realpath g) || fail=1
# With relative path
$no_g_cmd
cp -Z f g || fail=1
# -Z overrides -a
$no_g_cmd
cp -Z -a f g || fail=1
# -Z doesn't take an arg
$no_g_cmd
cp -Z "$ctx" f g && fail=1

# Explicit context
$no_g_cmd
# Explicitly defaulting to the global $ctx should work
cp --context="$ctx" f g || fail=1
# --context overrides -a
$no_g_cmd
cp -a --context="$ctx" f g || fail=1
done

# Mutually exlusive options
cp -Z --preserve=context f g && fail=1
cp --preserve=context -Z f g && fail=1
cp --preserve=context --context="$ctx" f g && fail=1

Exit $fail
1 change: 1 addition & 0 deletions tests/local.mk
Expand Up @@ -566,6 +566,7 @@ all_tests = \
tests/mkdir/parents.sh \
tests/mkdir/perm.sh \
tests/mkdir/selinux.sh \
tests/mkdir/restorecon.sh \
tests/mkdir/special-1.sh \
tests/mkdir/t-slash.sh \
tests/mv/acl.sh \
Expand Down
72 changes: 72 additions & 0 deletions tests/mkdir/restorecon.sh
@@ -0,0 +1,72 @@
#!/bin/sh
# test mkdir, mknod, mkfifo -Z

# Copyright (C) 2013 Free Software Foundation, Inc.

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
print_ver_ mkdir
require_selinux_


get_selinux_type() { ls -Zd "$1" | sed -n 's/.*:\(.*_t\):.*/\1/p'; }

mkdir subdir || framework_failure_
chcon 'root:object_r:tmp_t:s0' subdir || framework_failure_
cd subdir

# --- mkdir -Z ---
# Since in a tmp_t dir, dirs can be created as user_tmp_t ...
mkdir standard || framework_failure_
mkdir restored || framework_failure_
if restorecon restored 2>/dev/null; then
# ... but when restored can be set to user_home_t
# So ensure the type for these mkdir -Z cases matches
# the directory type as set by restorecon.
mkdir -Z single || fail=1
# Run these as separate processes in case global context
# set for an arg, impacts on another arg
for dir in single_p single_p/existing multi/ple; do
mkdir -Zp "$dir" || fail=1
done
restored_type=$(get_selinux_type 'restored')
test "$(get_selinux_type 'single')" = "$restored_type" || fail=1
test "$(get_selinux_type 'single_p')" = "$restored_type" || fail=1
test "$(get_selinux_type 'single_p/existing')" = "$restored_type" || fail=1
test "$(get_selinux_type 'multi')" = "$restored_type" || fail=1
test "$(get_selinux_type 'multi/ple')" = "$restored_type" || fail=1
fi
if test "$fail" = '1'; then
ls -UZd standard restored
ls -UZd single single_p single_p/existing multi multi/ple
fi

# --- mknod -Z and mkfifo -Z ---
# Assume if selinux present that we can create fifos
for cmd_w_arg in 'mknod' 'mkfifo'; do
# In OpenBSD's /bin/sh, mknod is a shell built-in.
# Running via "env" ensures we run our program and not the built-in.
basename="$cmd_w_arg"
test "$basename" = 'mknod' && nt='p' || nt=''
env -- $cmd_w_arg $basename $nt || fail=1
env -- $cmd_w_arg ${basename}_restore $nt || fail=1
if restorecon ${basename}_restore 2>/dev/null; then
env -- $cmd_w_arg -Z ${basename}_Z $nt || fail=1
restored_type=$(get_selinux_type "${basename}_restore")
test "$(get_selinux_type ${basename}_Z)" = "$restored_type" || fail=1
fi
done

Exit $fail
2 changes: 1 addition & 1 deletion tests/mkdir/selinux.sh
Expand Up @@ -32,7 +32,7 @@ msg="failed to set default file creation context to '$c':"
for cmd_w_arg in 'mkdir dir' 'mknod b p' 'mkfifo f'; do
# In OpenBSD's /bin/sh, mknod is a shell built-in.
# Running via "env" ensures we run our program and not the built-in.
env -- $cmd_w_arg -Z $c 2> out && fail=1
env -- $cmd_w_arg --context=$c 2> out && fail=1
set $cmd_w_arg; cmd=$1
echo "$cmd: $msg" > exp || fail=1

Expand Down