The first two tries of this patchset were late last year. This is
a labeling API that provides a common way to map from various string
namespaces into security contexts.
This version of the patchset simplifies the lookup model down to
(string,number) to context. There are no void pointers or variadic
functions which was one of the objections to the previous patchsets.
A lot of the file contexts stuff such as the inode tracking support
has also been dropped with the understanding that this stuff should
be in the setfiles code, not libselinux. This is a pure lookup
interface only.
This patchset includes two backends, for file contexts and media
contexts. Future work would include libsemanage interfaces for
managing the data the way the file contexts data is currently done.
This patch includes the interface and generic handle code. Tested
with some sample input values, worked OK.
Signed-off-by: Eamon Walsh <ewalsh@tycho.nsa.gov>
---
include/selinux/label.h | 108 +++++++++++++++++++++++++++++++++++++
src/label.c | 140 ++++++++++++++++++++++++++++++++++++++++++++++++
src/label_internal.h | 49 ++++++++++++++++
3 files changed, 297 insertions(+)
Index: libselinux/include/selinux/label.h
===================================================================
--- libselinux/include/selinux/label.h (revision 0)
+++ libselinux/include/selinux/label.h (revision 0)
@@ -0,0 +1,108 @@
+/*
+ * Labeling interface for userspace object managers and others.
+ *
+ * Author : Eamon Walsh <ewalsh@tycho.nsa.gov>
+ */
+#ifndef _SELABEL_H_
+#define _SELABEL_H_
+
+#include <sys/types.h>
+#include <selinux/selinux.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Opaque type used for all label handles.
+ */
+
+typedef struct selabel_rec *selabel_handle_t;
+
+/*
+ * Available backends.
+ */
+
+/* file contexts */
+#define SELABEL_CTX_FILE 0
+/* media contexts */
+#define SELABEL_CTX_MEDIA 1
+
+/*
+ * Available options
+ */
+
+/* validate contexts before returning them (boolean value) */
+#define SELABEL_OPT_VALIDATE 1
+/* don't use local customizations to backend data (boolean value) */
+#define SELABEL_OPT_BASEONLY 2
+/* specify an alternate path to use when loading backend data */
+#define SELABEL_OPT_PATH 3
+/* specify a filename prefix to focus the search operation (file contexts) */
+#define SELABEL_OPT_PREFIX 4
+
+struct selabel_opt {
+ int type;
+ const char *value;
+};
+
+/*
+ * Label operations
+ */
+
+/**
+ * selabel_open - Create a labeling handle.
+ * @backend: one of the constants specifying a supported labeling backend.
+ * @opts: array of selabel_opt structures specifying label options or NULL.
+ * @nopts: number of elements in opts array or zero for no options.
+ *
+ * Open a labeling backend for use. The available backend identifiers are
+ * listed above. Options may be provided via the opts parameter; available
+ * options are listed above. Not all options may be supported by every
+ * backend. Return value is the created handle on success or NULL with
+ * @errno set on failure.
+ */
+selabel_handle_t selabel_open(unsigned int backend, struct selabel_opt *opts,
+ size_t nopts);
+
+/**
+ * selabel_close - Close a labeling handle.
+ * @handle: specifies handle to close
+ *
+ * Destroy the specified handle, closing files, freeing allocated memory,
+ * etc. The handle may not be further used after it has been closed.
+ */
+void selabel_close(selabel_handle_t handle);
+
+/**
+ * selabel_lookup - Perform labeling lookup operation.
+ * @handle: specifies backend instance to query
+ * @con: returns the appropriate context with which to label the object
+ * @key: string input to lookup operation
+ * @type: numeric input to the lookup operation
+ *
+ * Perform a labeling lookup operation. Return %0 on success, -%1 with
+ * @errno set on failure. The key and type arguments are the inputs to the
+ * lookup operation; appropriate values are dictated by the backend in use.
+ * The result is returned in the memory pointed to by @con and must be freed
+ * by the user with freecon().
+ */
+int selabel_lookup(selabel_handle_t handle, security_context_t *con,
+ const char *key, int type);
+int selabel_lookup_raw(selabel_handle_t handle, security_context_t *con,
+ const char *key, int type);
+
+/**
+ * selabel_stats - log labeling operation statistics.
+ * @handle: specifies backend instance to query
+ *
+ * Log a message with information about the number of queries performed,
+ * number of unused matching entries, or other operational statistics.
+ * Message is backend-specific, some backends may not output a message.
+ */
+void selabel_stats(selabel_handle_t handle);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* _SELABEL_H_ */
Index: libselinux/src/label_internal.h
===================================================================
--- libselinux/src/label_internal.h (revision 0)
+++ libselinux/src/label_internal.h (revision 0)
@@ -0,0 +1,49 @@
+/*
+ * This file describes the internal interface used by the labeler
+ * for calling the user-supplied memory allocation, validation,
+ * and locking routine.
+ *
+ * Author : Eamon Walsh <ewalsh@epoch.ncsc.mil>
+ */
+#ifndef _SELABEL_INTERNAL_H_
+#define _SELABEL_INTERNAL_H_
+
+#include <stdlib.h>
+#include <stdarg.h>
+#include <selinux/selinux.h>
+#include <selinux/label.h>
+#include "dso.h"
+
+/*
+ * Installed backends
+ */
+int selabel_file_init(struct selabel_rec *rec, struct selabel_opt *opts,
+ size_t nopts) hidden;
+int selabel_media_init(struct selabel_rec *rec, struct selabel_opt *opts,
+ size_t nopts) hidden;
+
+/*
+ * Labeling internal structures
+ */
+struct selabel_lookup_rec {
+ security_context_t ctx_raw;
+ security_context_t ctx_trans;
+ int validated;
+};
+
+struct selabel_rec {
+ /* arguments that were passed to selabel_open */
+ unsigned int backend;
+ int validating;
+
+ /* labeling operations */
+ struct selabel_lookup_rec *(*func_lookup) (struct selabel_rec *h,
+ const char *key, int type);
+ void (*func_close) (struct selabel_rec *h);
+ void (*func_stats) (struct selabel_rec *h);
+
+ /* supports backend-specific state information */
+ void *data;
+};
+
+#endif /* _SELABEL_INTERNAL_H_ */
Index: libselinux/src/label.c
===================================================================
--- libselinux/src/label.c (revision 0)
+++ libselinux/src/label.c (revision 0)
@@ -0,0 +1,140 @@
+/*
+ * Generalized labeling frontend for userspace object managers.
+ *
+ * Author : Eamon Walsh <ewalsh@epoch.ncsc.mil>
+ */
+
+#include <sys/types.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "callbacks.h"
+#include "label_internal.h"
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+
+typedef int (*selabel_initfunc)(struct selabel_rec *rec,
+ struct selabel_opt *opts, size_t nopts);
+
+static selabel_initfunc initfuncs[] = {
+ &selabel_file_init,
+ &selabel_media_init,
+};
+
+/*
+ * Validation functions
+ */
+
+static inline int selabel_is_validate_set(struct selabel_opt *opts, size_t n)
+{
+ while (n--)
+ if (opts[n].type == SELABEL_OPT_VALIDATE)
+ return !!opts[n].value;
+
+ return 0;
+}
+
+static int selabel_validate(struct selabel_rec *rec,
+ struct selabel_lookup_rec *contexts)
+{
+ int rc = 0;
+
+ if (!rec->validating || contexts->validated)
+ goto out;
+
+ rc = selinux_validate(&contexts->ctx_raw);
+ if (rc < 0) {
+ selinux_log(SELINUX_ERROR, "invalid context %s\n",
+ contexts->ctx_raw);
+ goto out;
+ }
+
+ contexts->validated = 1;
+out:
+ return rc;
+}
+
+/*
+ * Public API
+ */
+
+selabel_handle_t selabel_open(unsigned int backend, struct selabel_opt *opts,
+ size_t nopts)
+{
+ struct selabel_rec *rec = NULL;
+
+ if (backend >= ARRAY_SIZE(initfuncs)) {
+ errno = EINVAL;
+ goto out;
+ }
+
+ rec = (struct selabel_rec *)malloc(sizeof(*rec));
+ if (!rec)
+ goto out;
+
+ memset(rec, 0, sizeof(*rec));
+ rec->backend = backend;
+ rec->validating = selabel_is_validate_set(opts, nopts);
+
+ if ((*initfuncs[backend])(rec, opts, nopts))
+ free(rec);
+
+out:
+ return rec;
+}
+
+static struct selabel_lookup_rec *
+selabel_lookup_common(struct selabel_rec *rec, int translating,
+ const char *key, int type)
+{
+ struct selabel_lookup_rec *lr = rec->func_lookup(rec, key, type);
+ if (!lr)
+ return NULL;
+
+ if (selabel_validate(rec, lr))
+ return NULL;
+
+ if (translating &&
+ selinux_raw_to_trans_context(lr->ctx_raw, &lr->ctx_trans))
+ return NULL;
+
+ return lr;
+}
+
+int selabel_lookup(struct selabel_rec *rec, security_context_t *con,
+ const char *key, int type)
+{
+ struct selabel_lookup_rec *lr;
+
+ lr = selabel_lookup_common(rec, 1, key, type);
+ if (!lr)
+ return -1;
+
+ *con = strdup(lr->ctx_trans);
+ return *con ? 0 : -1;
+}
+
+int selabel_lookup_raw(struct selabel_rec *rec, security_context_t *con,
+ const char *key, int type)
+{
+ struct selabel_lookup_rec *lr;
+
+ lr = selabel_lookup_common(rec, 0, key, type);
+ if (!lr)
+ return -1;
+
+ *con = strdup(lr->ctx_raw);
+ return *con ? 0 : -1;
+}
+
+void selabel_close(struct selabel_rec *rec)
+{
+ rec->func_close(rec);
+ free(rec);
+}
+
+void selabel_stats(struct selabel_rec *rec)
+{
+ rec->func_stats(rec);
+}
--
Eamon Walsh <ewalsh@tycho.nsa.gov>
National Security Agency
--
This message was distributed to subscribers of the selinux mailing list.
If you no longer wish to subscribe, send mail to majordomo@tycho.nsa.gov with
the words "unsubscribe selinux" without quotes as the message.
This patch includes media backend code.
Signed-off-by: Eamon Walsh <ewalsh@tycho.nsa.gov>
---
label_media.c | 219 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 219 insertions(+)
Index: libselinux/src/label_media.c
===================================================================
--- libselinux/src/label_media.c (revision 0)
+++ libselinux/src/label_media.c (revision 0)
@@ -0,0 +1,219 @@
+/*
+ * Media contexts backend for labeling system
+ *
+ * Author : Eamon Walsh <ewalsh@tycho.nsa.gov>
+ */
+
+#include <sys/stat.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdio_ext.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include "callbacks.h"
+#include "label_internal.h"
+
+/*
+ * Internals
+ */
+
+/* A context specification. */
+typedef struct spec {
+ struct selabel_lookup_rec lr; /* holds contexts for lookup result */
+ char *key; /* key string */
+ int matches; /* number of matches made during operation */
+} spec_t;
+
+struct saved_data {
+ unsigned int nspec;
+ spec_t *spec_arr;
+};
+
+static int process_line(const char *path, char *line_buf, int pass,
+ unsigned lineno, struct selabel_rec *rec)
+{
+ struct saved_data *data = (struct saved_data *)rec->data;
+ int items;
+ char *buf_p;
+ char *key, *context;
+
+ buf_p = line_buf;
+ while (isspace(*buf_p))
+ buf_p++;
+ /* Skip comment lines and empty lines. */
+ if (*buf_p == '#' || *buf_p == 0)
+ return 0;
+ items = sscanf(line_buf, "%as %as ", &key, &context);
+ if (items < 2) {
+ selinux_log(SELINUX_WARNING,
+ "%s: line %d is missing fields, skipping\n", path,
+ lineno);
+ if (items == 1)
+ free(key);
+ return 0;
+ }
+
+ if (pass == 1) {
+ data->spec_arr[data->nspec].key = key;
+ data->spec_arr[data->nspec].lr.ctx_raw = context;
+ }
+
+ data->nspec++;
+ if (pass == 0) {
+ free(key);
+ free(context);
+ }
+ return 0;
+}
+
+static int init(struct selabel_rec *rec, struct selabel_opt *opts,
+ size_t n)
+{
+ FILE *fp;
+ struct saved_data *data = (struct saved_data *)rec->data;
+ const char *path = NULL;
+ char *line_buf = NULL;
+ size_t line_len = 0;
+ int status = -1;
+ unsigned int lineno, pass, maxnspec;
+ struct stat sb;
+
+ /* Process arguments */
+ while (n--)
+ switch(opts[n].type) {
+ case SELABEL_OPT_PATH:
+ path = opts[n].value;
+ break;
+ }
+
+ /* Open the specification file. */
+ if (!path)
+ path = selinux_media_context_path();
+ if ((fp = fopen(path, "r")) == NULL)
+ return -1;
+ __fsetlocking(fp, FSETLOCKING_BYCALLER);
+
+ if (fstat(fileno(fp), &sb) < 0)
+ return -1;
+ if (!S_ISREG(sb.st_mode)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /*
+ * Perform two passes over the specification file.
+ * The first pass counts the number of specifications and
+ * performs simple validation of the input. At the end
+ * of the first pass, the spec array is allocated.
+ * The second pass performs detailed validation of the input
+ * and fills in the spec array.
+ */
+ maxnspec = UINT_MAX / sizeof(spec_t);
+ for (pass = 0; pass < 2; pass++) {
+ lineno = 0;
+ data->nspec = 0;
+ while (getline(&line_buf, &line_len, fp) > 0 &&
+ data->nspec < maxnspec) {
+ if (process_line(path, line_buf, pass, ++lineno, rec))
+ goto finish;
+ }
+ lineno = 0;
+
+ if (pass == 0) {
+ if (data->nspec == 0) {
+ status = 0;
+ goto finish;
+ }
+ data->spec_arr = malloc(sizeof(spec_t)*data->nspec);
+ if (data->spec_arr == NULL)
+ goto finish;
+ memset(data->spec_arr, 0, sizeof(spec_t)*data->nspec);
+ maxnspec = data->nspec;
+ rewind(fp);
+ }
+ }
+ free(line_buf);
+
+ status = 0;
+finish:
+ fclose(fp);
+ return status;
+}
+
+/*
+ * Backend interface routines
+ */
+static void close(struct selabel_rec *rec)
+{
+ struct saved_data *data = (struct saved_data *)rec->data;
+ struct spec *spec, *spec_arr = data->spec_arr;
+ unsigned int i;
+
+ for (i = 0; i < data->nspec; i++) {
+ spec = &spec_arr[i];
+ free(spec->key);
+ free(spec->lr.ctx_raw);
+ free(spec->lr.ctx_trans);
+ }
+
+ if (spec_arr)
+ free(spec_arr);
+
+ memset(data, 0, sizeof(*data));
+}
+
+static struct selabel_lookup_rec *lookup(struct selabel_rec *rec,
+ const char *key,
+ int type __attribute__((unused)))
+{
+ struct saved_data *data = (struct saved_data *)rec->data;
+ spec_t *spec_arr = data->spec_arr;
+ unsigned int i;
+
+ for (i = 0; i < data->nspec; i++) {
+ if (!strncmp(spec_arr[i].key, key, strlen(key) + 1))
+ break;
+ if (!strncmp(spec_arr[i].key, "*", 2))
+ break;
+ }
+
+ if (i >= data->nspec) {
+ /* No matching specification. */
+ errno = ENOENT;
+ return NULL;
+ }
+
+ spec_arr[i].matches++;
+ return &spec_arr[i].lr;
+}
+
+static void stats(struct selabel_rec *rec)
+{
+ struct saved_data *data = (struct saved_data *)rec->data;
+ unsigned int i, total = 0;
+
+ for (i = 0; i < data->nspec; i++)
+ total += data->spec_arr[i].matches;
+
+ selinux_log(SELINUX_INFO, "%u entries, %u matches made\n",
+ data->nspec, total);
+}
+
+int selabel_media_init(struct selabel_rec *rec, struct selabel_opt *opts,
+ size_t nopts)
+{
+ struct saved_data *data;
+
+ data = (struct saved_data *)malloc(sizeof(*data));
+ if (!data)
+ return -1;
+ memset(data, 0, sizeof(*data));
+
+ rec->data = data;
+ rec->func_close = &close;
+ rec->func_lookup = &lookup;
+ rec->func_stats = &stats;
+
+ return init(rec, opts, nopts);
+}
--
Eamon Walsh <ewalsh@tycho.nsa.gov>
National Security Agency
--
This message was distributed to subscribers of the selinux mailing list.
If you no longer wish to subscribe, send mail to majordomo@tycho.nsa.gov with
the words "unsubscribe selinux" without quotes as the message.
This patch includes the file contexts backend code.
Signed-off-by: Eamon Walsh <ewalsh@tycho.nsa.gov>
---
label_file.c | 637 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 637 insertions(+)
Index: libselinux/src/label_file.c
===================================================================
--- libselinux/src/label_file.c (revision 0)
+++ libselinux/src/label_file.c (revision 0)
@@ -0,0 +1,637 @@
+/*
+ * File contexts backend for labeling system
+ *
+ * Author : Eamon Walsh <ewalsh@tycho.nsa.gov>
+ * Author : Stephen Smalley <sds@tycho.nsa.gov>
+ *
+ * This library derived in part from setfiles and the setfiles.pl script
+ * developed by Secure Computing Corporation.
+ */
+
+#include <fcntl.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdio_ext.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <regex.h>
+#include "callbacks.h"
+#include "label_internal.h"
+
+/*
+ * Internals, mostly moved over from matchpathcon.c
+ */
+
+/* A file security context specification. */
+typedef struct spec {
+ struct selabel_lookup_rec lr; /* holds contexts for lookup result */
+ char *regex_str; /* regular expession string for diagnostics */
+ char *type_str; /* type string for diagnostic messages */
+ regex_t regex; /* compiled regular expression */
+ mode_t mode; /* mode format value */
+ int matches; /* number of matching pathnames */
+ int hasMetaChars; /* regular expression has meta-chars */
+ int stem_id; /* indicates which stem-compression item */
+} spec_t;
+
+/* A regular expression stem */
+typedef struct stem {
+ char *buf;
+ int len;
+} stem_t;
+
+/* Our stored configuration */
+struct saved_data {
+ /*
+ * The array of specifications, initially in the same order as in
+ * the specification file. Sorting occurs based on hasMetaChars.
+ */
+ spec_t *spec_arr;
+ unsigned int nspec;
+
+ /*
+ * The array of regular expression stems.
+ */
+ stem_t *stem_arr;
+ int num_stems;
+ int alloc_stems;
+};
+
+/* Return the length of the text that can be considered the stem, returns 0
+ * if there is no identifiable stem */
+static int get_stem_from_spec(const char *const buf)
+{
+ const char *tmp = strchr(buf + 1, '/');
+ const char *ind;
+
+ if (!tmp)
+ return 0;
+
+ for (ind = buf; ind < tmp; ind++) {
+ if (strchr(".^$?*+|[({", (int)*ind))
+ return 0;
+ }
+ return tmp - buf;
+}
+
+/* return the length of the text that is the stem of a file name */
+static int get_stem_from_file_name(const char *const buf)
+{
+ const char *tmp = strchr(buf + 1, '/');
+
+ if (!tmp)
+ return 0;
+ return tmp - buf;
+}
+
+/* find the stem of a file spec, returns the index into stem_arr for a new
+ * or existing stem, (or -1 if there is no possible stem - IE for a file in
+ * the root directory or a regex that is too complex for us). Makes buf
+ * point to the text AFTER the stem. */
+static int find_stem_from_spec(struct saved_data *data, const char **buf)
+{
+ int i, num = data->num_stems;
+ int stem_len = get_stem_from_spec(*buf);
+
+ if (!stem_len)
+ return -1;
+ for (i = 0; i < num; i++) {
+ if (stem_len == data->stem_arr[i].len
+ && !strncmp(*buf, data->stem_arr[i].buf, stem_len)) {
+ *buf += stem_len;
+ return i;
+ }
+ }
+ if (data->alloc_stems == num) {
+ stem_t *tmp_arr;
+ data->alloc_stems = data->alloc_stems * 2 + 16;
+ tmp_arr = realloc(data->stem_arr,
+ sizeof(stem_t) * data->alloc_stems);
+ if (!tmp_arr)
+ return -1;
+ data->stem_arr = tmp_arr;
+ }
+ data->stem_arr[num].len = stem_len;
+ data->stem_arr[num].buf = malloc(stem_len + 1);
+ if (!data->stem_arr[num].buf)
+ return -1;
+ memcpy(data->stem_arr[num].buf, *buf, stem_len);
+ data->stem_arr[num].buf[stem_len] = '\0';
+ data->num_stems++;
+ *buf += stem_len;
+ return num;
+}
+
+/* find the stem of a file name, returns the index into stem_arr (or -1 if
+ * there is no match - IE for a file in the root directory or a regex that is
+ * too complex for us). Makes buf point to the text AFTER the stem. */
+static int find_stem_from_file(struct saved_data *data, const char **buf)
+{
+ int i;
+ int stem_len = get_stem_from_file_name(*buf);
+
+ if (!stem_len)
+ return -1;
+ for (i = 0; i < data->num_stems; i++) {
+ if (stem_len == data->stem_arr[i].len
+ && !strncmp(*buf, data->stem_arr[i].buf, stem_len)) {
+ *buf += stem_len;
+ return i;
+ }
+ }
+ return -1;
+}
+
+/*
+ * Warn about duplicate specifications.
+ */
+static void nodups_specs(struct saved_data *data, const char *path)
+{
+ unsigned int ii, jj;
+ struct spec *curr_spec, *spec_arr = data->spec_arr;
+
+ for (ii = 0; ii < data->nspec; ii++) {
+ curr_spec = &spec_arr[ii];
+ for (jj = ii + 1; jj < data->nspec; jj++) {
+ if ((!strcmp
+ (spec_arr[jj].regex_str, curr_spec->regex_str))
+ && (!spec_arr[jj].mode || !curr_spec->mode
+ || spec_arr[jj].mode == curr_spec->mode)) {
+ if (strcmp
+ (spec_arr[jj].lr.ctx_raw,
+ curr_spec->lr.ctx_raw)) {
+ selinux_log
+ (SELINUX_WARNING,
+ "%s: Multiple different specifications for %s (%s and %s).\n",
+ path, curr_spec->regex_str,
+ spec_arr[jj].lr.ctx_raw,
+ curr_spec->lr.ctx_raw);
+ } else {
+ selinux_log
+ (SELINUX_WARNING,
+ "%s: Multiple same specifications for %s.\n",
+ path, curr_spec->regex_str);
+ }
+ }
+ }
+ }
+}
+
+/* Determine if the regular expression specification has any meta characters. */
+static void spec_hasMetaChars(struct spec *spec)
+{
+ char *c;
+ int len;
+ char *end;
+
+ c = spec->regex_str;
+ len = strlen(spec->regex_str);
+ end = c + len;
+
+ spec->hasMetaChars = 0;
+
+ /* Look at each character in the RE specification string for a
+ * meta character. Return when any meta character reached. */
+ while (c != end) {
+ switch (*c) {
+ case '.':
+ case '^':
+ case '$':
+ case '?':
+ case '*':
+ case '+':
+ case '|':
+ case '[':
+ case '(':
+ case '{':
+ spec->hasMetaChars = 1;
+ return;
+ case '\\': /* skip the next character */
+ c++;
+ break;
+ default:
+ break;
+
+ }
+ c++;
+ }
+ return;
+}
+
+static int process_line(struct saved_data *data,
+ const char *path, const char *prefix,
+ char *line_buf, int pass, unsigned lineno)
+{
+ int items, len, regerr;
+ char *buf_p, *regex, *anchored_regex, *type, *context;
+ const char *reg_buf;
+ spec_t *spec_arr = data->spec_arr;
+ unsigned int nspec = data->nspec;
+
+ len = strlen(line_buf);
+ if (line_buf[len - 1] == '\n')
+ line_buf[len - 1] = 0;
+ buf_p = line_buf;
+ while (isspace(*buf_p))
+ buf_p++;
+ /* Skip comment lines and empty lines. */
+ if (*buf_p == '#' || *buf_p == 0)
+ return 0;
+ items = sscanf(line_buf, "%as %as %as", ®ex, &type, &context);
+ if (items < 2) {
+ selinux_log(SELINUX_WARNING,
+ "%s: line %d is missing fields, skipping\n", path,
+ lineno);
+ return 0;
+ } else if (items == 2) {
+ /* The type field is optional. */
+ free(context);
+ context = type;
+ type = 0;
+ }
+
+ reg_buf = regex;
+ len = get_stem_from_spec(reg_buf);
+ if (len && prefix && strncmp(prefix, regex, len)) {
+ /* Stem of regex does not match requested prefix, discard. */
+ free(regex);
+ free(type);
+ free(context);
+ return 0;
+ }
+
+ if (pass == 1) {
+ /* On the second pass, compile and store the specification in spec. */
+ char *cp;
+ spec_arr[nspec].stem_id = find_stem_from_spec(data, ®_buf);
+ spec_arr[nspec].regex_str = regex;
+
+ /* Anchor the regular expression. */
+ len = strlen(reg_buf);
+ cp = anchored_regex = malloc(len + 3);
+ if (!anchored_regex)
+ return -1;
+ /* Create ^...$ regexp. */
+ *cp++ = '^';
+ cp = mempcpy(cp, reg_buf, len);
+ *cp++ = '$';
+ *cp = '\0';
+
+ /* Compile the regular expression. */
+ regerr =
+ regcomp(&spec_arr[nspec].regex,
+ anchored_regex, REG_EXTENDED | REG_NOSUB);
+ if (regerr != 0) {
+ size_t errsz = 0;
+ char *errbuf = NULL;
+ errsz = regerror(regerr, &spec_arr[nspec].regex,
+ errbuf, errsz);
+ if (errsz)
+ errbuf = malloc(errsz);
+ if (errbuf)
+ (void)regerror(regerr,
+ &spec_arr[nspec].regex,
+ errbuf, errsz);
+ selinux_log(SELINUX_WARNING,
+ "%s: line %d has invalid regex %s: %s\n",
+ path, lineno, anchored_regex,
+ (errbuf ? errbuf : "out of memory"));
+ free(anchored_regex);
+ return 0;
+ }
+ free(anchored_regex);
+
+ /* Convert the type string to a mode format */
+ spec_arr[nspec].type_str = type;
+ spec_arr[nspec].mode = 0;
+ if (!type)
+ goto skip_type;
+ len = strlen(type);
+ if (type[0] != '-' || len != 2) {
+ selinux_log(SELINUX_WARNING,
+ "%s: line %d has invalid file type %s\n",
+ path, lineno, type);
+ return 0;
+ }
+ switch (type[1]) {
+ case 'b':
+ spec_arr[nspec].mode = S_IFBLK;
+ break;
+ case 'c':
+ spec_arr[nspec].mode = S_IFCHR;
+ break;
+ case 'd':
+ spec_arr[nspec].mode = S_IFDIR;
+ break;
+ case 'p':
+ spec_arr[nspec].mode = S_IFIFO;
+ break;
+ case 'l':
+ spec_arr[nspec].mode = S_IFLNK;
+ break;
+ case 's':
+ spec_arr[nspec].mode = S_IFSOCK;
+ break;
+ case '-':
+ spec_arr[nspec].mode = S_IFREG;
+ break;
+ default:
+ selinux_log(SELINUX_WARNING,
+ "%s: line %d has invalid file type %s\n",
+ path, lineno, type);
+ return 0;
+ }
+
+ skip_type:
+ spec_arr[nspec].lr.ctx_raw = context;
+
+ /* Determine if specification has
+ * any meta characters in the RE */
+ spec_hasMetaChars(&spec_arr[nspec]);
+ }
+
+ data->nspec = ++nspec;
+ if (pass == 0) {
+ free(regex);
+ if (type)
+ free(type);
+ free(context);
+ }
+ return 0;
+}
+
+static int init(struct selabel_rec *rec, struct selabel_opt *opts, size_t n)
+{
+ struct saved_data *data = (struct saved_data *)rec->data;
+ const char *path = NULL;
+ const char *prefix = NULL;
+ FILE *fp;
+ FILE *localfp = NULL;
+ FILE *homedirfp = NULL;
+ char local_path[PATH_MAX + 1];
+ char homedir_path[PATH_MAX + 1];
+ char *line_buf = NULL;
+ size_t line_len = 0;
+ unsigned int lineno, pass, i, j, maxnspec;
+ spec_t *spec_copy = NULL;
+ int status = -1, baseonly = 0;
+ struct stat sb;
+
+ /* Process arguments */
+ while (n--)
+ switch(opts[n].type) {
+ case SELABEL_OPT_PATH:
+ path = opts[n].value;
+ break;
+ case SELABEL_OPT_PREFIX:
+ prefix = opts[n].value;
+ break;
+ case SELABEL_OPT_BASEONLY:
+ baseonly = !!opts[n].value;
+ break;
+ }
+
+ /* Open the specification file. */
+ if (!path)
+ path = selinux_file_context_path();
+ if ((fp = fopen(path, "r")) == NULL)
+ return -1;
+ __fsetlocking(fp, FSETLOCKING_BYCALLER);
+
+ if (fstat(fileno(fp), &sb) < 0)
+ return -1;
+ if (!S_ISREG(sb.st_mode)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (!baseonly) {
+ snprintf(homedir_path, sizeof(homedir_path), "%s.homedirs",
+ path);
+ homedirfp = fopen(homedir_path, "r");
+ if (homedirfp != NULL)
+ __fsetlocking(homedirfp, FSETLOCKING_BYCALLER);
+
+ snprintf(local_path, sizeof(local_path), "%s.local", path);
+ localfp = fopen(local_path, "r");
+ if (localfp != NULL)
+ __fsetlocking(localfp, FSETLOCKING_BYCALLER);
+ }
+
+ /*
+ * Perform two passes over the specification file.
+ * The first pass counts the number of specifications and
+ * performs simple validation of the input. At the end
+ * of the first pass, the spec array is allocated.
+ * The second pass performs detailed validation of the input
+ * and fills in the spec array.
+ */
+ maxnspec = UINT_MAX / sizeof(spec_t);
+ for (pass = 0; pass < 2; pass++) {
+ lineno = 0;
+ data->nspec = 0;
+ while (getline(&line_buf, &line_len, fp) > 0
+ && data->nspec < maxnspec) {
+ if (process_line(data, path, prefix, line_buf,
+ pass, ++lineno) != 0)
+ goto finish;
+ }
+ lineno = 0;
+ if (homedirfp)
+ while (getline(&line_buf, &line_len, homedirfp) > 0
+ && data->nspec < maxnspec) {
+ if (process_line
+ (data, homedir_path, prefix,
+ line_buf, pass, ++lineno) != 0)
+ goto finish;
+ }
+
+ lineno = 0;
+ if (localfp)
+ while (getline(&line_buf, &line_len, localfp) > 0
+ && data->nspec < maxnspec) {
+ if (process_line
+ (data, local_path, prefix, line_buf,
+ pass, ++lineno) != 0)
+ goto finish;
+ }
+
+ if (pass == 0) {
+ if (data->nspec == 0) {
+ status = 0;
+ goto finish;
+ }
+ if (NULL == (data->spec_arr =
+ malloc(sizeof(spec_t) * data->nspec)))
+ goto finish;
+ memset(data->spec_arr, 0, sizeof(spec_t)*data->nspec);
+ maxnspec = data->nspec;
+ rewind(fp);
+ if (homedirfp)
+ rewind(homedirfp);
+ if (localfp)
+ rewind(localfp);
+ }
+ }
+ free(line_buf);
+
+ /* Move exact pathname specifications to the end. */
+ spec_copy = malloc(sizeof(spec_t) * data->nspec);
+ if (!spec_copy)
+ goto finish;
+ j = 0;
+ for (i = 0; i < data->nspec; i++)
+ if (data->spec_arr[i].hasMetaChars)
+ memcpy(&spec_copy[j++],
+ &data->spec_arr[i], sizeof(spec_t));
+ for (i = 0; i < data->nspec; i++)
+ if (!data->spec_arr[i].hasMetaChars)
+ memcpy(&spec_copy[j++],
+ &data->spec_arr[i], sizeof(spec_t));
+ free(data->spec_arr);
+ data->spec_arr = spec_copy;
+
+ nodups_specs(data, path);
+
+ status = 0;
+finish:
+ fclose(fp);
+ if (data->spec_arr != spec_copy)
+ free(data->spec_arr);
+ if (homedirfp)
+ fclose(homedirfp);
+ if (localfp)
+ fclose(localfp);
+ return status;
+}
+
+/*
+ * Backend interface routines
+ */
+static void close(struct selabel_rec *rec)
+{
+ struct saved_data *data = (struct saved_data *)rec->data;
+ struct spec *spec;
+ struct stem *stem;
+ unsigned int i;
+
+ for (i = 0; i < data->nspec; i++) {
+ spec = &data->spec_arr[i];
+ free(spec->regex_str);
+ free(spec->type_str);
+ free(spec->lr.ctx_raw);
+ free(spec->lr.ctx_trans);
+ regfree(&spec->regex);
+ }
+
+ for (i = 0; i < (unsigned int)data->num_stems; i++) {
+ stem = &data->stem_arr[i];
+ free(stem->buf);
+ }
+
+ if (data->spec_arr)
+ free(data->spec_arr);
+ if (data->stem_arr)
+ free(data->stem_arr);
+
+ memset(data, 0, sizeof(*data));
+}
+
+static struct selabel_lookup_rec *lookup(struct selabel_rec *rec,
+ const char *key, int type)
+{
+ struct saved_data *data = (struct saved_data *)rec->data;
+ spec_t *spec_arr = data->spec_arr;
+ int i, rc, file_stem;
+ mode_t mode = (mode_t)type;
+ const char *buf = key;
+
+ if (!data->nspec) {
+ errno = ENOENT;
+ return NULL;
+ }
+
+ file_stem = find_stem_from_file(data, &buf);
+ mode &= S_IFMT;
+
+ /*
+ * Check for matching specifications in reverse order, so that
+ * the last matching specification is used.
+ */
+ for (i = data->nspec - 1; i >= 0; i--) {
+ /* if the spec in question matches no stem or has the same
+ * stem as the file AND if the spec in question has no mode
+ * specified or if the mode matches the file mode then we do
+ * a regex check */
+ if ((spec_arr[i].stem_id == -1
+ || spec_arr[i].stem_id == file_stem)
+ && (!mode || !spec_arr[i].mode
+ || mode == spec_arr[i].mode)) {
+ if (spec_arr[i].stem_id == -1)
+ rc = regexec(&spec_arr[i].regex, key, 0, 0, 0);
+ else
+ rc = regexec(&spec_arr[i].regex, buf, 0, 0, 0);
+
+ if (rc == 0) {
+ spec_arr[i].matches++;
+ break;
+ }
+ if (rc == REG_NOMATCH)
+ continue;
+ /* else it's an error */
+ return NULL;
+ }
+ }
+
+ if (i < 0 || strcmp(spec_arr[i].lr.ctx_raw, "<<none>>") == 0) {
+ /* No matching specification. */
+ errno = ENOENT;
+ return NULL;
+ }
+
+ return &spec_arr[i].lr;
+}
+
+static void stats(struct selabel_rec *rec)
+{
+ struct saved_data *data = (struct saved_data *)rec->data;
+ unsigned int i, nspec = data->nspec;
+ spec_t *spec_arr = data->spec_arr;
+
+ for (i = 0; i < nspec; i++) {
+ if (spec_arr[i].matches == 0) {
+ if (spec_arr[i].type_str) {
+ selinux_log(SELINUX_WARNING,
+ "Warning! No matches for (%s, %s, %s)\n",
+ spec_arr[i].regex_str,
+ spec_arr[i].type_str,
+ spec_arr[i].lr.ctx_raw);
+ } else {
+ selinux_log(SELINUX_WARNING,
+ "Warning! No matches for (%s, %s)\n",
+ spec_arr[i].regex_str,
+ spec_arr[i].lr.ctx_raw);
+ }
+ }
+ }
+}
+
+int selabel_file_init(struct selabel_rec *rec, struct selabel_opt *opts,
+ size_t nopts)
+{
+ struct saved_data *data;
+
+ data = (struct saved_data *)malloc(sizeof(*data));
+ if (!data)
+ return -1;
+ memset(data, 0, sizeof(*data));
+
+ rec->data = data;
+ rec->func_close = &close;
+ rec->func_stats = &stats;
+ rec->func_lookup = &lookup;
+
+ return init(rec, opts, nopts);
+}
--
Eamon Walsh <ewalsh@tycho.nsa.gov>
National Security Agency
--
This message was distributed to subscribers of the selinux mailing list.
If you no longer wish to subscribe, send mail to majordomo@tycho.nsa.gov with
the words "unsubscribe selinux" without quotes as the message.
On Mon, 2007-06-11 at 15:38 -0400, Eamon Walsh wrote:
> This patch includes the file contexts backend code.
Logically, then, I would expect you to replace the matchpathcon.c and
matchmediacon.c code with stub functions that use this new
implementation, then verify that they still work as expected. We don't
want two independent implementations in the library. Then we can
deprecate them and eventually phase them out, but that will take some
time, as they are used by code outside of the core selinux userland,
including things like rpm, udev, etc.
>
> Signed-off-by: Eamon Walsh <ewalsh@tycho.nsa.gov>
> ---
>
> label_file.c | 637 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 637 insertions(+)
>
>
> Index: libselinux/src/label_file.c
> ===================================================================
> --- libselinux/src/label_file.c (revision 0)
> +++ libselinux/src/label_file.c (revision 0)
> @@ -0,0 +1,637 @@
> +/*
> + * File contexts backend for labeling system
> + *
> + * Author : Eamon Walsh <ewalsh@tycho.nsa.gov>
> + * Author : Stephen Smalley <sds@tycho.nsa.gov>
> + *
> + * This library derived in part from setfiles and the setfiles.pl script
> + * developed by Secure Computing Corporation.
> + */
> +
> +#include <fcntl.h>
> +#include <stdarg.h>
> +#include <string.h>
> +#include <stdio.h>
> +#include <stdio_ext.h>
> +#include <ctype.h>
> +#include <errno.h>
> +#include <limits.h>
> +#include <regex.h>
> +#include "callbacks.h"
> +#include "label_internal.h"
> +
> +/*
> + * Internals, mostly moved over from matchpathcon.c
> + */
> +
> +/* A file security context specification. */
> +typedef struct spec {
> + struct selabel_lookup_rec lr; /* holds contexts for lookup result */
> + char *regex_str; /* regular expession string for diagnostics */
> + char *type_str; /* type string for diagnostic messages */
> + regex_t regex; /* compiled regular expression */
> + mode_t mode; /* mode format value */
> + int matches; /* number of matching pathnames */
> + int hasMetaChars; /* regular expression has meta-chars */
> + int stem_id; /* indicates which stem-compression item */
> +} spec_t;
> +
> +/* A regular expression stem */
> +typedef struct stem {
> + char *buf;
> + int len;
> +} stem_t;
> +
> +/* Our stored configuration */
> +struct saved_data {
> + /*
> + * The array of specifications, initially in the same order as in
> + * the specification file. Sorting occurs based on hasMetaChars.
> + */
> + spec_t *spec_arr;
> + unsigned int nspec;
> +
> + /*
> + * The array of regular expression stems.
> + */
> + stem_t *stem_arr;
> + int num_stems;
> + int alloc_stems;
> +};
> +
> +/* Return the length of the text that can be considered the stem, returns 0
> + * if there is no identifiable stem */
> +static int get_stem_from_spec(const char *const buf)
> +{
> + const char *tmp = strchr(buf + 1, '/');
> + const char *ind;
> +
> + if (!tmp)
> + return 0;
> +
> + for (ind = buf; ind < tmp; ind++) {
> + if (strchr(".^$?*+|[({", (int)*ind))
> + return 0;
> + }
> + return tmp - buf;
> +}
> +
> +/* return the length of the text that is the stem of a file name */
> +static int get_stem_from_file_name(const char *const buf)
> +{
> + const char *tmp = strchr(buf + 1, '/');
> +
> + if (!tmp)
> + return 0;
> + return tmp - buf;
> +}
> +
> +/* find the stem of a file spec, returns the index into stem_arr for a new
> + * or existing stem, (or -1 if there is no possible stem - IE for a file in
> + * the root directory or a regex that is too complex for us). Makes buf
> + * point to the text AFTER the stem. */
> +static int find_stem_from_spec(struct saved_data *data, const char **buf)
> +{
> + int i, num = data->num_stems;
> + int stem_len = get_stem_from_spec(*buf);
> +
> + if (!stem_len)
> + return -1;
> + for (i = 0; i < num; i++) {
> + if (stem_len == data->stem_arr[i].len
> + && !strncmp(*buf, data->stem_arr[i].buf, stem_len)) {
> + *buf += stem_len;
> + return i;
> + }
> + }
> + if (data->alloc_stems == num) {
> + stem_t *tmp_arr;
> + data->alloc_stems = data->alloc_stems * 2 + 16;
> + tmp_arr = realloc(data->stem_arr,
> + sizeof(stem_t) * data->alloc_stems);
> + if (!tmp_arr)
> + return -1;
> + data->stem_arr = tmp_arr;
> + }
> + data->stem_arr[num].len = stem_len;
> + data->stem_arr[num].buf = malloc(stem_len + 1);
> + if (!data->stem_arr[num].buf)
> + return -1;
> + memcpy(data->stem_arr[num].buf, *buf, stem_len);
> + data->stem_arr[num].buf[stem_len] = '\0';
> + data->num_stems++;
> + *buf += stem_len;
> + return num;
> +}
> +
> +/* find the stem of a file name, returns the index into stem_arr (or -1 if
> + * there is no match - IE for a file in the root directory or a regex that is
> + * too complex for us). Makes buf point to the text AFTER the stem. */
> +static int find_stem_from_file(struct saved_data *data, const char **buf)
> +{
> + int i;
> + int stem_len = get_stem_from_file_name(*buf);
> +
> + if (!stem_len)
> + return -1;
> + for (i = 0; i < data->num_stems; i++) {
> + if (stem_len == data->stem_arr[i].len
> + && !strncmp(*buf, data->stem_arr[i].buf, stem_len)) {
> + *buf += stem_len;
> + return i;
> + }
> + }
> + return -1;
> +}
> +
> +/*
> + * Warn about duplicate specifications.
> + */
> +static void nodups_specs(struct saved_data *data, const char *path)
> +{
> + unsigned int ii, jj;
> + struct spec *curr_spec, *spec_arr = data->spec_arr;
> +
> + for (ii = 0; ii < data->nspec; ii++) {
> + curr_spec = &spec_arr[ii];
> + for (jj = ii + 1; jj < data->nspec; jj++) {
> + if ((!strcmp
> + (spec_arr[jj].regex_str, curr_spec->regex_str))
> + && (!spec_arr[jj].mode || !curr_spec->mode
> + || spec_arr[jj].mode == curr_spec->mode)) {
> + if (strcmp
> + (spec_arr[jj].lr.ctx_raw,
> + curr_spec->lr.ctx_raw)) {
> + selinux_log
> + (SELINUX_WARNING,
> + "%s: Multiple different specifications for %s (%s and %s).\n",
> + path, curr_spec->regex_str,
> + spec_arr[jj].lr.ctx_raw,
> + curr_spec->lr.ctx_raw);
> + } else {
> + selinux_log
> + (SELINUX_WARNING,
> + "%s: Multiple same specifications for %s.\n",
> + path, curr_spec->regex_str);
> + }
> + }
> + }
> + }
> +}
> +
> +/* Determine if the regular expression specification has any meta characters. */
> +static void spec_hasMetaChars(struct spec *spec)
> +{
> + char *c;
> + int len;
> + char *end;
> +
> + c = spec->regex_str;
> + len = strlen(spec->regex_str);
> + end = c + len;
> +
> + spec->hasMetaChars = 0;
> +
> + /* Look at each character in the RE specification string for a
> + * meta character. Return when any meta character reached. */
> + while (c != end) {
> + switch (*c) {
> + case '.':
> + case '^':
> + case '$':
> + case '?':
> + case '*':
> + case '+':
> + case '|':
> + case '[':
> + case '(':
> + case '{':
> + spec->hasMetaChars = 1;
> + return;
> + case '\\': /* skip the next character */
> + c++;
> + break;
> + default:
> + break;
> +
> + }
> + c++;
> + }
> + return;
> +}
> +
> +static int process_line(struct saved_data *data,
> + const char *path, const char *prefix,
> + char *line_buf, int pass, unsigned lineno)
> +{
> + int items, len, regerr;
> + char *buf_p, *regex, *anchored_regex, *type, *context;
> + const char *reg_buf;
> + spec_t *spec_arr = data->spec_arr;
> + unsigned int nspec = data->nspec;
> +
> + len = strlen(line_buf);
> + if (line_buf[len - 1] == '\n')
> + line_buf[len - 1] = 0;
> + buf_p = line_buf;
> + while (isspace(*buf_p))
> + buf_p++;
> + /* Skip comment lines and empty lines. */
> + if (*buf_p == '#' || *buf_p == 0)
> + return 0;
> + items = sscanf(line_buf, "%as %as %as", ®ex, &type, &context);
> + if (items < 2) {
> + selinux_log(SELINUX_WARNING,
> + "%s: line %d is missing fields, skipping\n", path,
> + lineno);
> + return 0;
> + } else if (items == 2) {
> + /* The type field is optional. */
> + free(context);
> + context = type;
> + type = 0;
> + }
> +
> + reg_buf = regex;
> + len = get_stem_from_spec(reg_buf);
> + if (len && prefix && strncmp(prefix, regex, len)) {
> + /* Stem of regex does not match requested prefix, discard. */
> + free(regex);
> + free(type);
> + free(context);
> + return 0;
> + }
> +
> + if (pass == 1) {
> + /* On the second pass, compile and store the specification in spec. */
> + char *cp;
> + spec_arr[nspec].stem_id = find_stem_from_spec(data, ®_buf);
> + spec_arr[nspec].regex_str = regex;
> +
> + /* Anchor the regular expression. */
> + len = strlen(reg_buf);
> + cp = anchored_regex = malloc(len + 3);
> + if (!anchored_regex)
> + return -1;
> + /* Create ^...$ regexp. */
> + *cp++ = '^';
> + cp = mempcpy(cp, reg_buf, len);
> + *cp++ = '$';
> + *cp = '\0';
> +
> + /* Compile the regular expression. */
> + regerr =
> + regcomp(&spec_arr[nspec].regex,
> + anchored_regex, REG_EXTENDED | REG_NOSUB);
> + if (regerr != 0) {
> + size_t errsz = 0;
> + char *errbuf = NULL;
> + errsz = regerror(regerr, &spec_arr[nspec].regex,
> + errbuf, errsz);
> + if (errsz)
> + errbuf = malloc(errsz);
> + if (errbuf)
> + (void)regerror(regerr,
> + &spec_arr[nspec].regex,
> + errbuf, errsz);
> + selinux_log(SELINUX_WARNING,
> + "%s: line %d has invalid regex %s: %s\n",
> + path, lineno, anchored_regex,
> + (errbuf ? errbuf : "out of memory"));
> + free(anchored_regex);
> + return 0;
> + }
> + free(anchored_regex);
> +
> + /* Convert the type string to a mode format */
> + spec_arr[nspec].type_str = type;
> + spec_arr[nspec].mode = 0;
> + if (!type)
> + goto skip_type;
> + len = strlen(type);
> + if (type[0] != '-' || len != 2) {
> + selinux_log(SELINUX_WARNING,
> + "%s: line %d has invalid file type %s\n",
> + path, lineno, type);
> + return 0;
> + }
> + switch (type[1]) {
> + case 'b':
> + spec_arr[nspec].mode = S_IFBLK;
> + break;
> + case 'c':
> + spec_arr[nspec].mode = S_IFCHR;
> + break;
> + case 'd':
> + spec_arr[nspec].mode = S_IFDIR;
> + break;
> + case 'p':
> + spec_arr[nspec].mode = S_IFIFO;
> + break;
> + case 'l':
> + spec_arr[nspec].mode = S_IFLNK;
> + break;
> + case 's':
> + spec_arr[nspec].mode = S_IFSOCK;
> + break;
> + case '-':
> + spec_arr[nspec].mode = S_IFREG;
> + break;
> + default:
> + selinux_log(SELINUX_WARNING,
> + "%s: line %d has invalid file type %s\n",
> + path, lineno, type);
> + return 0;
> + }
> +
> + skip_type:
> + spec_arr[nspec].lr.ctx_raw = context;
> +
> + /* Determine if specification has
> + * any meta characters in the RE */
> + spec_hasMetaChars(&spec_arr[nspec]);
> + }
> +
> + data->nspec = ++nspec;
> + if (pass == 0) {
> + free(regex);
> + if (type)
> + free(type);
> + free(context);
> + }
> + return 0;
> +}
> +
> +static int init(struct selabel_rec *rec, struct selabel_opt *opts, size_t n)
> +{
> + struct saved_data *data = (struct saved_data *)rec->data;
> + const char *path = NULL;
> + const char *prefix = NULL;
> + FILE *fp;
> + FILE *localfp = NULL;
> + FILE *homedirfp = NULL;
> + char local_path[PATH_MAX + 1];
> + char homedir_path[PATH_MAX + 1];
> + char *line_buf = NULL;
> + size_t line_len = 0;
> + unsigned int lineno, pass, i, j, maxnspec;
> + spec_t *spec_copy = NULL;
> + int status = -1, baseonly = 0;
> + struct stat sb;
> +
> + /* Process arguments */
> + while (n--)
> + switch(opts[n].type) {
> + case SELABEL_OPT_PATH:
> + path = opts[n].value;
> + break;
> + case SELABEL_OPT_PREFIX:
> + prefix = opts[n].value;
> + break;
> + case SELABEL_OPT_BASEONLY:
> + baseonly = !!opts[n].value;
> + break;
> + }
> +
> + /* Open the specification file. */
> + if (!path)
> + path = selinux_file_context_path();
> + if ((fp = fopen(path, "r")) == NULL)
> + return -1;
> + __fsetlocking(fp, FSETLOCKING_BYCALLER);
> +
> + if (fstat(fileno(fp), &sb) < 0)
> + return -1;
> + if (!S_ISREG(sb.st_mode)) {
> + errno = EINVAL;
> + return -1;
> + }
> +
> + if (!baseonly) {
> + snprintf(homedir_path, sizeof(homedir_path), "%s.homedirs",
> + path);
> + homedirfp = fopen(homedir_path, "r");
> + if (homedirfp != NULL)
> + __fsetlocking(homedirfp, FSETLOCKING_BYCALLER);
> +
> + snprintf(local_path, sizeof(local_path), "%s.local", path);
> + localfp = fopen(local_path, "r");
> + if (localfp != NULL)
> + __fsetlocking(localfp, FSETLOCKING_BYCALLER);
> + }
> +
> + /*
> + * Perform two passes over the specification file.
> + * The first pass counts the number of specifications and
> + * performs simple validation of the input. At the end
> + * of the first pass, the spec array is allocated.
> + * The second pass performs detailed validation of the input
> + * and fills in the spec array.
> + */
> + maxnspec = UINT_MAX / sizeof(spec_t);
> + for (pass = 0; pass < 2; pass++) {
> + lineno = 0;
> + data->nspec = 0;
> + while (getline(&line_buf, &line_len, fp) > 0
> + && data->nspec < maxnspec) {
> + if (process_line(data, path, prefix, line_buf,
> + pass, ++lineno) != 0)
> + goto finish;
> + }
> + lineno = 0;
> + if (homedirfp)
> + while (getline(&line_buf, &line_len, homedirfp) > 0
> + && data->nspec < maxnspec) {
> + if (process_line
> + (data, homedir_path, prefix,
> + line_buf, pass, ++lineno) != 0)
> + goto finish;
> + }
> +
> + lineno = 0;
> + if (localfp)
> + while (getline(&line_buf, &line_len, localfp) > 0
> + && data->nspec < maxnspec) {
> + if (process_line
> + (data, local_path, prefix, line_buf,
> + pass, ++lineno) != 0)
> + goto finish;
> + }
> +
> + if (pass == 0) {
> + if (data->nspec == 0) {
> + status = 0;
> + goto finish;
> + }
> + if (NULL == (data->spec_arr =
> + malloc(sizeof(spec_t) * data->nspec)))
> + goto finish;
> + memset(data->spec_arr, 0, sizeof(spec_t)*data->nspec);
> + maxnspec = data->nspec;
> + rewind(fp);
> + if (homedirfp)
> + rewind(homedirfp);
> + if (localfp)
> + rewind(localfp);
> + }
> + }
> + free(line_buf);
> +
> + /* Move exact pathname specifications to the end. */
> + spec_copy = malloc(sizeof(spec_t) * data->nspec);
> + if (!spec_copy)
> + goto finish;
> + j = 0;
> + for (i = 0; i < data->nspec; i++)
> + if (data->spec_arr[i].hasMetaChars)
> + memcpy(&spec_copy[j++],
> + &data->spec_arr[i], sizeof(spec_t));
> + for (i = 0; i < data->nspec; i++)
> + if (!data->spec_arr[i].hasMetaChars)
> + memcpy(&spec_copy[j++],
> + &data->spec_arr[i], sizeof(spec_t));
> + free(data->spec_arr);
> + data->spec_arr = spec_copy;
> +
> + nodups_specs(data, path);
> +
> + status = 0;
> +finish:
> + fclose(fp);
> + if (data->spec_arr != spec_copy)
> + free(data->spec_arr);
> + if (homedirfp)
> + fclose(homedirfp);
> + if (localfp)
> + fclose(localfp);
> + return status;
> +}
> +
> +/*
> + * Backend interface routines
> + */
> +static void close(struct selabel_rec *rec)
> +{
> + struct saved_data *data = (struct saved_data *)rec->data;
> + struct spec *spec;
> + struct stem *stem;
> + unsigned int i;
> +
> + for (i = 0; i < data->nspec; i++) {
> + spec = &data->spec_arr[i];
> + free(spec->regex_str);
> + free(spec->type_str);
> + free(spec->lr.ctx_raw);
> + free(spec->lr.ctx_trans);
> + regfree(&spec->regex);
> + }
> +
> + for (i = 0; i < (unsigned int)data->num_stems; i++) {
> + stem = &data->stem_arr[i];
> + free(stem->buf);
> + }
> +
> + if (data->spec_arr)
> + free(data->spec_arr);
> + if (data->stem_arr)
> + free(data->stem_arr);
> +
> + memset(data, 0, sizeof(*data));
> +}
> +
> +static struct selabel_lookup_rec *lookup(struct selabel_rec *rec,
> + const char *key, int type)
> +{
> + struct saved_data *data = (struct saved_data *)rec->data;
> + spec_t *spec_arr = data->spec_arr;
> + int i, rc, file_stem;
> + mode_t mode = (mode_t)type;
> + const char *buf = key;
> +
> + if (!data->nspec) {
> + errno = ENOENT;
> + return NULL;
> + }
> +
> + file_stem = find_stem_from_file(data, &buf);
> + mode &= S_IFMT;
> +
> + /*
> + * Check for matching specifications in reverse order, so that
> + * the last matching specification is used.
> + */
> + for (i = data->nspec - 1; i >= 0; i--) {
> + /* if the spec in question matches no stem or has the same
> + * stem as the file AND if the spec in question has no mode
> + * specified or if the mode matches the file mode then we do
> + * a regex check */
> + if ((spec_arr[i].stem_id == -1
> + || spec_arr[i].stem_id == file_stem)
> + && (!mode || !spec_arr[i].mode
> + || mode == spec_arr[i].mode)) {
> + if (spec_arr[i].stem_id == -1)
> + rc = regexec(&spec_arr[i].regex, key, 0, 0, 0);
> + else
> + rc = regexec(&spec_arr[i].regex, buf, 0, 0, 0);
> +
> + if (rc == 0) {
> + spec_arr[i].matches++;
> + break;
> + }
> + if (rc == REG_NOMATCH)
> + continue;
> + /* else it's an error */
> + return NULL;
> + }
> + }
> +
> + if (i < 0 || strcmp(spec_arr[i].lr.ctx_raw, "<<none>>") == 0) {
> + /* No matching specification. */
> + errno = ENOENT;
> + return NULL;
> + }
> +
> + return &spec_arr[i].lr;
> +}
> +
> +static void stats(struct selabel_rec *rec)
> +{
> + struct saved_data *data = (struct saved_data *)rec->data;
> + unsigned int i, nspec = data->nspec;
> + spec_t *spec_arr = data->spec_arr;
> +
> + for (i = 0; i < nspec; i++) {
> + if (spec_arr[i].matches == 0) {
> + if (spec_arr[i].type_str) {
> + selinux_log(SELINUX_WARNING,
> + "Warning! No matches for (%s, %s, %s)\n",
> + spec_arr[i].regex_str,
> + spec_arr[i].type_str,
> + spec_arr[i].lr.ctx_raw);
> + } else {
> + selinux_log(SELINUX_WARNING,
> + "Warning! No matches for (%s, %s)\n",
> + spec_arr[i].regex_str,
> + spec_arr[i].lr.ctx_raw);
> + }
> + }
> + }
> +}
> +
> +int selabel_file_init(struct selabel_rec *rec, struct selabel_opt *opts,
> + size_t nopts)
> +{
> + struct saved_data *data;
> +
> + data = (struct saved_data *)malloc(sizeof(*data));
> + if (!data)
> + return -1;
> + memset(data, 0, sizeof(*data));
> +
> + rec->data = data;
> + rec->func_close = &close;
> + rec->func_stats = &stats;
> + rec->func_lookup = &lookup;
> +
> + return init(rec, opts, nopts);
> +}
>
>
--
Stephen Smalley
National Security Agency
--
This message was distributed to subscribers of the selinux mailing list.
If you no longer wish to subscribe, send mail to majordomo@tycho.nsa.gov with
the words "unsubscribe selinux" without quotes as the message.
On Mon, 2007-06-11 at 15:27 -0400, Eamon Walsh wrote:
> The first two tries of this patchset were late last year. This is
> a labeling API that provides a common way to map from various string
> namespaces into security contexts.
>
> This version of the patchset simplifies the lookup model down to
> (string,number) to context. There are no void pointers or variadic
> functions which was one of the objections to the previous patchsets.
> A lot of the file contexts stuff such as the inode tracking support
> has also been dropped with the understanding that this stuff should
> be in the setfiles code, not libselinux. This is a pure lookup
> interface only.
>
> This patchset includes two backends, for file contexts and media
> contexts. Future work would include libsemanage interfaces for
> managing the data the way the file contexts data is currently done.
>
> This patch includes the interface and generic handle code. Tested
> with some sample input values, worked OK.
>
Can you post some client code so we can get a feel for the api?
> Signed-off-by: Eamon Walsh <ewalsh@tycho.nsa.gov>
> ---
>
> include/selinux/label.h | 108 +++++++++++++++++++++++++++++++++++++
> src/label.c | 140 ++++++++++++++++++++++++++++++++++++++++++++++++
> src/label_internal.h | 49 ++++++++++++++++
> 3 files changed, 297 insertions(+)
>
>
> Index: libselinux/include/selinux/label.h
> ===================================================================
> --- libselinux/include/selinux/label.h (revision 0)
> +++ libselinux/include/selinux/label.h (revision 0)
> @@ -0,0 +1,108 @@
> +/*
> + * Labeling interface for userspace object managers and others.
> + *
> + * Author : Eamon Walsh <ewalsh@tycho.nsa.gov>
> + */
> +#ifndef _SELABEL_H_
> +#define _SELABEL_H_
> +
> +#include <sys/types.h>
> +#include <selinux/selinux.h>
> +
> +#ifdef __cplusplus
> +extern "C" {
> +#endif
> +
> +/*
> + * Opaque type used for all label handles.
> + */
> +
> +typedef struct selabel_rec *selabel_handle_t;
> +
Please - no more typedef'd pointers in libselinux. They are just
_wrong_.
Karl
--
This message was distributed to subscribers of the selinux mailing list.
If you no longer wish to subscribe, send mail to majordomo@tycho.nsa.gov with
the words "unsubscribe selinux" without quotes as the message.
Here's some test code that I've been using. The program looks up
the command line argument as a file first, then as a media label.
The file options are set up with a "matchpathcon_prefix" of /etc and
validation turned on.
I can produce a patch for setfiles, but that will take a little
longer since all the inode code that was placed into libselinux has
to be put back into setfiles.
#include <stdio.h>
#include <stdlib.h>
#include <selinux/selinux.h>
#include <selinux/label.h>
static void test_file(const char *str)
{
selabel_handle_t hnd;
security_context_t result;
int rc;
struct selabel_opt opts[] = {
{ SELABEL_OPT_PREFIX, "/etc" },
{ SELABEL_OPT_VALIDATE, "1" }
};
hnd = selabel_open(SELABEL_CTX_FILE, opts, 2);
if (!hnd) {
perror("selabel_open");
exit(1);
}
rc = selabel_lookup(hnd, &result, str, 0);
if (rc < 0) {
perror("selabel_lookup");
} else {
printf("File result is: %s\n", result);
freecon(result);
}
selabel_close(hnd);
}
static void test_media(const char *str)
{
selabel_handle_t hnd;
security_context_t result;
int rc;
hnd = selabel_open(SELABEL_CTX_MEDIA, NULL, 0);
if (!hnd) {
perror("selabel_open");
exit(1);
}
rc = selabel_lookup(hnd, &result, str, 0);
if (rc < 0) {
perror("selabel_lookup");
} else {
printf("Media result is: %s\n", result);
freecon(result);
}
selabel_close(hnd);
}
int main(int argc, char **argv)
{
if (argc != 2) {
fprintf(stderr, "usage\n");
return 1;
}
test_file(argv[1]);
test_media(argv[1]);
return 0;
}
--
Eamon Walsh <ewalsh@tycho.nsa.gov>
National Security Agency
--
This message was distributed to subscribers of the selinux mailing list.
If you no longer wish to subscribe, send mail to majordomo@tycho.nsa.gov with
the words "unsubscribe selinux" without quotes as the message.
This is the patch to setfiles, with the inode tracking moved out
of the library and back into the application code.
The one thing about moving the inode code back out is that the
behavior on a hard-link context conflict is different; it does
not know which match is the "last one" in the file_contexts file.
So yes, there is an ever-so-slight dependency. In this code, the
first link encountered wins.
Signed-off-by: Eamon Walsh <ewalsh@tycho.nsa.gov>
---
setfiles.c | 248 ++++++++++++++++++++++++++++++++++++++++++++++++-------------
1 file changed, 199 insertions(+), 49 deletions(-)
Index: policycoreutils/setfiles/setfiles.c
===================================================================
--- policycoreutils/setfiles/setfiles.c (revision 2474)
+++ policycoreutils/setfiles/setfiles.c (working copy)
@@ -16,6 +16,7 @@
#include <limits.h>
#include <sepol/sepol.h>
#include <selinux/selinux.h>
+#include <selinux/label.h>
#include <syslog.h>
#include <libgen.h>
#ifdef USE_AUDIT
@@ -70,21 +71,165 @@
static int abort_on_error; /* Abort the file tree walk upon an error. */
static int add_assoc; /* Track inode associations for conflict detection. */
static int nftw_flags; /* Flags to nftw, e.g. follow links, follow mounts */
-static int matchpathcon_flags; /* Flags to matchpathcon */
+static int base_only; /* Don't use local file_contexts customizations */
+static int ctx_validate; /* Validate contexts */
+static const char *altpath; /* Alternate path to file_contexts */
-static void
-#ifdef __GNUC__
- __attribute__ ((format(printf, 1, 2)))
-#endif
- qprintf(const char *fmt, ...)
+/* Label interface handle */
+static struct selabel_handle *hnd;
+
+/*
+ * An association between an inode and a context.
+ */
+typedef struct file_spec {
+ ino_t ino; /* inode number */
+ char *con; /* matched context */
+ char *file; /* full pathname */
+ struct file_spec *next; /* next association in hash bucket chain */
+} file_spec_t;
+
+/*
+ * The hash table of associations, hashed by inode number.
+ * Chaining is used for collisions, with elements ordered
+ * by inode number in each bucket. Each hash bucket has a dummy
+ * header.
+ */
+#define HASH_BITS 16
+#define HASH_BUCKETS (1 << HASH_BITS)
+#define HASH_MASK (HASH_BUCKETS-1)
+static file_spec_t *fl_head;
+
+/*
+ * Try to add an association between an inode and a context.
+ * If there is a different context that matched the inode,
+ * then use the first context that matched.
+ */
+int filespec_add(ino_t ino, const security_context_t con, const char *file)
{
- va_list ap;
- va_start(ap, fmt);
- if (!quiet)
- vfprintf(stdout, fmt, ap);
- va_end(ap);
+ file_spec_t *prevfl, *fl;
+ int h, ret;
+ struct stat sb;
+
+ if (!fl_head) {
+ fl_head = malloc(sizeof(file_spec_t) * HASH_BUCKETS);
+ if (!fl_head)
+ goto oom;
+ memset(fl_head, 0, sizeof(file_spec_t) * HASH_BUCKETS);
+ }
+
+ h = (ino + (ino >> HASH_BITS)) & HASH_MASK;
+ for (prevfl = &fl_head[h], fl = fl_head[h].next; fl;
+ prevfl = fl, fl = fl->next) {
+ if (ino == fl->ino) {
+ ret = lstat(fl->file, &sb);
+ if (ret < 0 || sb.st_ino != ino) {
+ freecon(fl->con);
+ free(fl->file);
+ fl->file = strdup(file);
+ if (!fl->file)
+ goto oom;
+ fl->con = strdup(con);
+ if (!fl->con)
+ goto oom;
+ return 1;
+ }
+
+ if (strcmp(fl->con, con) == 0)
+ return 1;
+
+ fprintf(stderr,
+ "%s: conflicting specifications for %s and %s, using %s.\n",
+ __FUNCTION__, file, fl->file, fl->con);
+ free(fl->file);
+ fl->file = strdup(file);
+ if (!fl->file)
+ goto oom;
+ return 1;
+ }
+
+ if (ino > fl->ino)
+ break;
+ }
+
+ fl = malloc(sizeof(file_spec_t));
+ if (!fl)
+ goto oom;
+ fl->ino = ino;
+ fl->con = strdup(con);
+ if (!fl->con)
+ goto oom_freefl;
+ fl->file = strdup(file);
+ if (!fl->file)
+ goto oom_freefl;
+ fl->next = prevfl->next;
+ prevfl->next = fl;
+ return 0;
+ oom_freefl:
+ free(fl);
+ oom:
+ fprintf(stderr,
+ "%s: insufficient memory for file label entry for %s\n",
+ __FUNCTION__, file);
+ return -1;
}
+/*
+ * Evaluate the association hash table distribution.
+ */
+void filespec_eval(void)
+{
+ file_spec_t *fl;
+ int h, used, nel, len, longest;
+
+ if (!fl_head)
+ return;
+
+ used = 0;
+ longest = 0;
+ nel = 0;
+ for (h = 0; h < HASH_BUCKETS; h++) {
+ len = 0;
+ for (fl = fl_head[h].next; fl; fl = fl->next) {
+ len++;
+ }
+ if (len)
+ used++;
+ if (len > longest)
+ longest = len;
+ nel += len;
+ }
+
+ printf
+ ("%s: hash table stats: %d elements, %d/%d buckets used, longest chain length %d\n",
+ __FUNCTION__, nel, used, HASH_BUCKETS, longest);
+}
+
+/*
+ * Destroy the association hash table.
+ */
+void filespec_destroy(void)
+{
+ file_spec_t *fl, *tmp;
+ int h;
+
+ if (!fl_head)
+ return;
+
+ for (h = 0; h < HASH_BUCKETS; h++) {
+ fl = fl_head[h].next;
+ while (fl) {
+ tmp = fl;
+ fl = fl->next;
+ freecon(tmp->con);
+ free(tmp->file);
+ free(tmp);
+ }
+ fl_head[h].next = NULL;
+ }
+ free(fl_head);
+ fl_head = NULL;
+}
+
static int add_exclude(const char *directory)
{
struct stat sb;
@@ -230,9 +375,9 @@
if (rootpath != NULL && name[0] == '\0')
/* this is actually the root dir of the alt root */
- return matchpathcon_index("/", sb->st_mode, con);
+ return selabel_lookup_raw(hnd, con, "/", sb->st_mode);
else
- return matchpathcon_index(name, sb->st_mode, con);
+ return selabel_lookup_raw(hnd, con, name, sb->st_mode);
}
void usage(const char *const name)
@@ -282,7 +427,7 @@
{
char *my_file = strdupa(file);
struct stat my_sb;
- int i, j, ret;
+ int ret;
char *context, *newcon;
int user_only_changed = 0;
size_t len = strlen(my_file);
@@ -293,10 +438,9 @@
if (len > 1 && my_file[len - 1] == '/')
my_file[len - 1] = 0;
- i = match(my_file, &my_sb, &newcon);
- if (i < 0)
- /* No matching specification. */
- return 0;
+ if (match(my_file, &my_sb, &newcon) < 0)
+ /* Check for no matching specification. */
+ return (errno == ENOENT) ? 0 : -1;
if (progress) {
count++;
@@ -317,14 +461,13 @@
* then use the last matching specification.
*/
if (add_assoc) {
- j = matchpathcon_filespec_add(my_sb.st_ino, i, my_file);
- if (j < 0)
+ ret = filespec_add(my_sb.st_ino, newcon, my_file);
+ if (ret < 0)
goto err;
- if (j != i) {
+ if (ret > 0)
/* There was already an association and it took precedence. */
goto out;
- }
}
if (debug) {
@@ -460,7 +603,7 @@
rootpathlen = len;
}
-int canoncon(const char *path, unsigned lineno, char **contextp)
+int canoncon(char **contextp)
{
char *context = *contextp, *tmpcon;
int valid = 1;
@@ -478,9 +621,6 @@
}
if (!valid) {
- fprintf(stderr, "%s: line %u has invalid context %s\n",
- path, lineno, context);
-
/* Exit immediately if we're in checking mode. */
if (policyfile)
exit(1);
@@ -557,10 +697,9 @@
out:
if (add_assoc) {
- set_matchpathcon_printf(&qprintf);
- matchpathcon_filespec_eval();
- set_matchpathcon_printf(NULL);
- matchpathcon_filespec_destroy();
+ if (!quiet)
+ filespec_eval();
+ filespec_destroy();
}
return rc;
@@ -605,14 +744,20 @@
int main(int argc, char **argv)
{
struct stat sb;
- int opt, rc, i = 0;
+ int opt, i = 0;
char *input_filename = NULL;
int use_input_file = 0;
char *buf = NULL;
size_t buf_len;
char *base;
+ struct selabel_opt opts[] = {
+ { SELABEL_OPT_VALIDATE, NULL },
+ { SELABEL_OPT_BASEONLY, NULL },
+ { SELABEL_OPT_PATH, NULL }
+ };
memset(excludeArray, 0, sizeof(excludeArray));
+ altpath = NULL;
progname = strdup(argv[0]);
if (!progname) {
@@ -637,7 +782,7 @@
abort_on_error = 1;
add_assoc = 1;
nftw_flags = FTW_PHYS | FTW_MOUNT;
- matchpathcon_flags = MATCHPATHCON_VALIDATE | MATCHPATHCON_NOTRANS;
+ ctx_validate = 1;
} else {
/*
* restorecon:
@@ -648,15 +793,15 @@
* Follows mounts,
* Does lazy validation of contexts upon use.
*/
- if (strcmp(base, RESTORECON))
- qprintf("Executed with an unrecognized name (%s), defaulting to %s behavior.\n", base, RESTORECON);
+ if (strcmp(base, RESTORECON) && !quiet)
+ printf("Executed with an unrecognized name (%s), defaulting to %s behavior.\n", base, RESTORECON);
iamrestorecon = 1;
recurse = 0;
expand_realpath = 1;
abort_on_error = 0;
add_assoc = 0;
nftw_flags = FTW_PHYS;
- matchpathcon_flags = MATCHPATHCON_NOTRANS;
+ ctx_validate = 0;
/* restorecon only: silent exit if no SELinux.
Allows unconditional execution by scripts. */
@@ -664,8 +809,6 @@
exit(0);
}
- set_matchpathcon_flags(matchpathcon_flags);
-
/* Process any options. */
while ((opt = getopt(argc, argv, "c:de:f:ilnpqrsvo:FRW")) > 0) {
switch (opt) {
@@ -700,9 +843,8 @@
/* Only process the specified file_contexts file, not
any .homedirs or .local files, and do not perform
context translations. */
- set_matchpathcon_flags(MATCHPATHCON_BASEONLY |
- MATCHPATHCON_NOTRANS |
- MATCHPATHCON_VALIDATE);
+ base_only = 1;
+ ctx_validate = 1;
break;
}
@@ -812,7 +954,8 @@
/* Use our own invalid context checking function so that
we can support either checking against the active policy or
checking against a binary policy file. */
- set_matchpathcon_canoncon(&canoncon);
+ selinux_set_callback(SELINUX_CB_VALIDATE,
+ (union selinux_callback)&canoncon);
if (stat(argv[optind], &sb) < 0) {
perror(argv[optind]);
@@ -824,19 +967,24 @@
exit(1);
}
- /* Load the file contexts configuration and check it. */
- rc = matchpathcon_init(argv[optind]);
- if (rc < 0) {
- perror(argv[optind]);
- exit(1);
- }
-
+ altpath = argv[optind];
optind++;
if (nerr)
exit(1);
}
+ /* Load the file contexts configuration and check it. */
+ opts[0].value = (char *)ctx_validate;
+ opts[1].value = (char *)base_only;
+ opts[2].value = altpath;
+
+ hnd = selabel_open(SELABEL_CTX_FILE, opts, 3);
+ if (!hnd) {
+ perror(altpath);
+ exit(1);
+ }
+
if (use_input_file) {
FILE *f = stdin;
ssize_t len;
@@ -863,8 +1011,10 @@
maybe_audit_mass_relabel();
if (warn_no_match)
- matchpathcon_checkmatches(argv[0]);
+ selabel_stats(hnd);
+ selabel_close(hnd);
+
if (outfile)
fclose(outfile);
--
Eamon Walsh <ewalsh@tycho.nsa.gov>
National Security Agency
--
This message was distributed to subscribers of the selinux mailing list.
If you no longer wish to subscribe, send mail to majordomo@tycho.nsa.gov with
the words "unsubscribe selinux" without quotes as the message.
On Tue, 2007-06-12 at 18:48 -0400, Eamon Walsh wrote:
> This is the patch to setfiles, with the inode tracking moved out
> of the library and back into the application code.
>
> The one thing about moving the inode code back out is that the
> behavior on a hard-link context conflict is different; it does
> not know which match is the "last one" in the file_contexts file.
> So yes, there is an ever-so-slight dependency. In this code, the
> first link encountered wins.
Breaks setfiles -c validation of a file contexts file against a given
policy file (used in policy builds and by libsemanage upon policy module
installs), e.g.
<insert bogus entry with foo_t into file_contexts>
Before:
$ setfiles -c /etc/selinux/targeted/policy/policy.21 file_contexts
libsepol.context_from_record: type foo_t is not defined
libsepol.context_from_record: could not create context structure
libsepol.context_from_string: could not create context structure
libsepol.sepol_context_to_sid: could not convert system_u:object_r:foo_t:s0 to sid
file_contexts: line 2 has invalid context system_u:object_r:foo_t:s0
$ echo $?
1
After:
$ setfiles -c /etc/selinux/targeted/policy/policy.21 file_contexts
<no output>
$ echo $?
0
>
> Signed-off-by: Eamon Walsh <ewalsh@tycho.nsa.gov>
> ---
>
> setfiles.c | 248 ++++++++++++++++++++++++++++++++++++++++++++++++-------------
> 1 file changed, 199 insertions(+), 49 deletions(-)
>
> Index: policycoreutils/setfiles/setfiles.c
> ===================================================================
> --- policycoreutils/setfiles/setfiles.c (revision 2474)
> +++ policycoreutils/setfiles/setfiles.c (working copy)
> @@ -16,6 +16,7 @@
> #include <limits.h>
> #include <sepol/sepol.h>
> #include <selinux/selinux.h>
> +#include <selinux/label.h>
> #include <syslog.h>
> #include <libgen.h>
> #ifdef USE_AUDIT
> @@ -70,21 +71,165 @@
> static int abort_on_error; /* Abort the file tree walk upon an error. */
> static int add_assoc; /* Track inode associations for conflict detection. */
> static int nftw_flags; /* Flags to nftw, e.g. follow links, follow mounts */
> -static int matchpathcon_flags; /* Flags to matchpathcon */
> +static int base_only; /* Don't use local file_contexts customizations */
> +static int ctx_validate; /* Validate contexts */
> +static const char *altpath; /* Alternate path to file_contexts */
>
> -static void
> -#ifdef __GNUC__
> - __attribute__ ((format(printf, 1, 2)))
> -#endif
> - qprintf(const char *fmt, ...)
> +/* Label interface handle */
> +static struct selabel_handle *hnd;
> +
> +/*
> + * An association between an inode and a context.
> + */
> +typedef struct file_spec {
> + ino_t ino; /* inode number */
> + char *con; /* matched context */
> + char *file; /* full pathname */
> + struct file_spec *next; /* next association in hash bucket chain */
> +} file_spec_t;
> +
> +/*
> + * The hash table of associations, hashed by inode number.
> + * Chaining is used for collisions, with elements ordered
> + * by inode number in each bucket. Each hash bucket has a dummy
> + * header.
> + */
> +#define HASH_BITS 16
> +#define HASH_BUCKETS (1 << HASH_BITS)
> +#define HASH_MASK (HASH_BUCKETS-1)
> +static file_spec_t *fl_head;
> +
> +/*
> + * Try to add an association between an inode and a context.
> + * If there is a different context that matched the inode,
> + * then use the first context that matched.
> + */
> +int filespec_add(ino_t ino, const security_context_t con, const char *file)
> {
> - va_list ap;
> - va_start(ap, fmt);
> - if (!quiet)
> - vfprintf(stdout, fmt, ap);
> - va_end(ap);
> + file_spec_t *prevfl, *fl;
> + int h, ret;
> + struct stat sb;
> +
> + if (!fl_head) {
> + fl_head = malloc(sizeof(file_spec_t) * HASH_BUCKETS);
> + if (!fl_head)
> + goto oom;
> + memset(fl_head, 0, sizeof(file_spec_t) * HASH_BUCKETS);
> + }
> +
> + h = (ino + (ino >> HASH_BITS)) & HASH_MASK;
> + for (prevfl = &fl_head[h], fl = fl_head[h].next; fl;
> + prevfl = fl, fl = fl->next) {
> + if (ino == fl->ino) {
> + ret = lstat(fl->file, &sb);
> + if (ret < 0 || sb.st_ino != ino) {
> + freecon(fl->con);
> + free(fl->file);
> + fl->file = strdup(file);
> + if (!fl->file)
> + goto oom;
> + fl->con = strdup(con);
> + if (!fl->con)
> + goto oom;
> + return 1;
> + }
> +
> + if (strcmp(fl->con, con) == 0)
> + return 1;
> +
> + fprintf(stderr,
> + "%s: conflicting specifications for %s and %s, using %s.\n",
> + __FUNCTION__, file, fl->file, fl->con);
> + free(fl->file);
> + fl->file = strdup(file);
> + if (!fl->file)
> + goto oom;
> + return 1;
> + }
> +
> + if (ino > fl->ino)
> + break;
> + }
> +
> + fl = malloc(sizeof(file_spec_t));
> + if (!fl)
> + goto oom;
> + fl->ino = ino;
> + fl->con = strdup(con);
> + if (!fl->con)
> + goto oom_freefl;
> + fl->file = strdup(file);
> + if (!fl->file)
> + goto oom_freefl;
> + fl->next = prevfl->next;
> + prevfl->next = fl;
> + return 0;
> + oom_freefl:
> + free(fl);
> + oom:
> + fprintf(stderr,
> + "%s: insufficient memory for file label entry for %s\n",
> + __FUNCTION__, file);
> + return -1;
> }
>
> +/*
> + * Evaluate the association hash table distribution.
> + */
> +void filespec_eval(void)
> +{
> + file_spec_t *fl;
> + int h, used, nel, len, longest;
> +
> + if (!fl_head)
> + return;
> +
> + used = 0;
> + longest = 0;
> + nel = 0;
> + for (h = 0; h < HASH_BUCKETS; h++) {
> + len = 0;
> + for (fl = fl_head[h].next; fl; fl = fl->next) {
> + len++;
> + }
> + if (len)
> + used++;
> + if (len > longest)
> + longest = len;
> + nel += len;
> + }
> +
> + printf
> + ("%s: hash table stats: %d elements, %d/%d buckets used, longest chain length %d\n",
> + __FUNCTION__, nel, used, HASH_BUCKETS, longest);
> +}
> +
> +/*
> + * Destroy the association hash table.
> + */
> +void filespec_destroy(void)
> +{
> + file_spec_t *fl, *tmp;
> + int h;
> +
> + if (!fl_head)
> + return;
> +
> + for (h = 0; h < HASH_BUCKETS; h++) {
> + fl = fl_head[h].next;
> + while (fl) {
> + tmp = fl;
> + fl = fl->next;
> + freecon(tmp->con);
> + free(tmp->file);
> + free(tmp);
> + }
> + fl_head[h].next = NULL;
> + }
> + free(fl_head);
> + fl_head = NULL;
> +}
> +
> static int add_exclude(const char *directory)
> {
> struct stat sb;
> @@ -230,9 +375,9 @@
>
> if (rootpath != NULL && name[0] == '\0')
> /* this is actually the root dir of the alt root */
> - return matchpathcon_index("/", sb->st_mode, con);
> + return selabel_lookup_raw(hnd, con, "/", sb->st_mode);
> else
> - return matchpathcon_index(name, sb->st_mode, con);
> + return selabel_lookup_raw(hnd, con, name, sb->st_mode);
> }
>
> void usage(const char *const name)
> @@ -282,7 +427,7 @@
> {
> char *my_file = strdupa(file);
> struct stat my_sb;
> - int i, j, ret;
> + int ret;
> char *context, *newcon;
> int user_only_changed = 0;
> size_t len = strlen(my_file);
> @@ -293,10 +438,9 @@
> if (len > 1 && my_file[len - 1] == '/')
> my_file[len - 1] = 0;
>
> - i = match(my_file, &my_sb, &newcon);
> - if (i < 0)
> - /* No matching specification. */
> - return 0;
> + if (match(my_file, &my_sb, &newcon) < 0)
> + /* Check for no matching specification. */
> + return (errno == ENOENT) ? 0 : -1;
>
> if (progress) {
> count++;
> @@ -317,14 +461,13 @@
> * then use the last matching specification.
> */
> if (add_assoc) {
> - j = matchpathcon_filespec_add(my_sb.st_ino, i, my_file);
> - if (j < 0)
> + ret = filespec_add(my_sb.st_ino, newcon, my_file);
> + if (ret < 0)
> goto err;
>
> - if (j != i) {
> + if (ret > 0)
> /* There was already an association and it took precedence. */
> goto out;
> - }
> }
>
> if (debug) {
> @@ -460,7 +603,7 @@
> rootpathlen = len;
> }
>
> -int canoncon(const char *path, unsigned lineno, char **contextp)
> +int canoncon(char **contextp)
> {
> char *context = *contextp, *tmpcon;
> int valid = 1;
> @@ -478,9 +621,6 @@
> }
>
> if (!valid) {
> - fprintf(stderr, "%s: line %u has invalid context %s\n",
> - path, lineno, context);
> -
> /* Exit immediately if we're in checking mode. */
> if (policyfile)
> exit(1);
> @@ -557,10 +697,9 @@
>
> out:
> if (add_assoc) {
> - set_matchpathcon_printf(&qprintf);
> - matchpathcon_filespec_eval();
> - set_matchpathcon_printf(NULL);
> - matchpathcon_filespec_destroy();
> + if (!quiet)
> + filespec_eval();
> + filespec_destroy();
> }
>
> return rc;
> @@ -605,14 +744,20 @@
> int main(int argc, char **argv)
> {
> struct stat sb;
> - int opt, rc, i = 0;
> + int opt, i = 0;
> char *input_filename = NULL;
> int use_input_file = 0;
> char *buf = NULL;
> size_t buf_len;
> char *base;
> + struct selabel_opt opts[] = {
> + { SELABEL_OPT_VALIDATE, NULL },
> + { SELABEL_OPT_BASEONLY, NULL },
> + { SELABEL_OPT_PATH, NULL }
> + };
>
> memset(excludeArray, 0, sizeof(excludeArray));
> + altpath = NULL;
>
> progname = strdup(argv[0]);
> if (!progname) {
> @@ -637,7 +782,7 @@
> abort_on_error = 1;
> add_assoc = 1;
> nftw_flags = FTW_PHYS | FTW_MOUNT;
> - matchpathcon_flags = MATCHPATHCON_VALIDATE | MATCHPATHCON_NOTRANS;
> + ctx_validate = 1;
> } else {
> /*
> * restorecon:
> @@ -648,15 +793,15 @@
> * Follows mounts,
> * Does lazy validation of contexts upon use.
> */
> - if (strcmp(base, RESTORECON))
> - qprintf("Executed with an unrecognized name (%s), defaulting to %s behavior.\n", base, RESTORECON);
> + if (strcmp(base, RESTORECON) && !quiet)
> + printf("Executed with an unrecognized name (%s), defaulting to %s behavior.\n", base, RESTORECON);
> iamrestorecon = 1;
> recurse = 0;
> expand_realpath = 1;
> abort_on_error = 0;
> add_assoc = 0;
> nftw_flags = FTW_PHYS;
> - matchpathcon_flags = MATCHPATHCON_NOTRANS;
> + ctx_validate = 0;
>
> /* restorecon only: silent exit if no SELinux.
> Allows unconditional execution by scripts. */
> @@ -664,8 +809,6 @@
> exit(0);
> }
>
> - set_matchpathcon_flags(matchpathcon_flags);
> -
> /* Process any options. */
> while ((opt = getopt(argc, argv, "c:de:f:ilnpqrsvo:FRW")) > 0) {
> switch (opt) {
> @@ -700,9 +843,8 @@
> /* Only process the specified file_contexts file, not
> any .homedirs or .local files, and do not perform
> context translations. */
> - set_matchpathcon_flags(MATCHPATHCON_BASEONLY |
> - MATCHPATHCON_NOTRANS |
> - MATCHPATHCON_VALIDATE);
> + base_only = 1;
> + ctx_validate = 1;
>
> break;
> }
> @@ -812,7 +954,8 @@
> /* Use our own invalid context checking function so that
> we can support either checking against the active policy or
> checking against a binary policy file. */
> - set_matchpathcon_canoncon(&canoncon);
> + selinux_set_callback(SELINUX_CB_VALIDATE,
> + (union selinux_callback)&canoncon);
>
> if (stat(argv[optind], &sb) < 0) {
> perror(argv[optind]);
> @@ -824,19 +967,24 @@
> exit(1);
> }
>
> - /* Load the file contexts configuration and check it. */
> - rc = matchpathcon_init(argv[optind]);
> - if (rc < 0) {
> - perror(argv[optind]);
> - exit(1);
> - }
> -
> + altpath = argv[optind];
> optind++;
>
> if (nerr)
> exit(1);
> }
>
> + /* Load the file contexts configuration and check it. */
> + opts[0].value = (char *)ctx_validate;
> + opts[1].value = (char *)base_only;
> + opts[2].value = altpath;
> +
> + hnd = selabel_open(SELABEL_CTX_FILE, opts, 3);
> + if (!hnd) {
> + perror(altpath);
> + exit(1);
> + }
> +
> if (use_input_file) {
> FILE *f = stdin;
> ssize_t len;
> @@ -863,8 +1011,10 @@
> maybe_audit_mass_relabel();
>
> if (warn_no_match)
> - matchpathcon_checkmatches(argv[0]);
> + selabel_stats(hnd);
>
> + selabel_close(hnd);
> +
> if (outfile)
> fclose(outfile);
>
>
>
--
Stephen Smalley
National Security Agency
--
This message was distributed to subscribers of the selinux mailing list.
If you no longer wish to subscribe, send mail to majordomo@tycho.nsa.gov with
the words "unsubscribe selinux" without quotes as the message.