summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Haines <richard_c_haines@btinternet.com>2015-06-04 20:03:41 +0100
committerJeff Vander Stoep <jeffv@google.com>2015-07-07 08:01:23 -0700
commit6d0b91bbb1ef05d2506f64cf37492e988063952b (patch)
tree5f174d6f95992884ebc02878e54d76073bbb72f5
parent5b4ddd760ebf58a1fb63509a0e5821a9bfe708c5 (diff)
downloadlibselinux-6d0b91bbb1ef05d2506f64cf37492e988063952b.tar.gz
libselinux: Enhance spec file support
This file labeling update brings libselinux into line with upstream selinux except for minor changes to support MacOS X and Bionic. Update file labeling to support a PCRE formatted binary file_contexts file. The file is generated on the host using sefcontext_compile. Should the bin file not be present (because the build process has not been implemented yet) or corrupt, the load process will fallback to the text file. Using the current emulator file_contexts file containing 321 lines (64 of which are blank or comments), the binary file loads approximately 50% faster than the text file. The processing of text spec files (file_contexts and property_contexts) now uses the read_spec_entries function instead of sscanf. To test the file_contexts.bin functionality requires an update to external/sepolicy/Android.mk to build the binary file for the device and also adding a 'file_contexts.bin' entry to the build/target/product/embedded.mk file under the SELinux PRODUCT_PACKAGES. These updates are available in the appropriate projects. Major changes in V2: label_file.c - Move process_line function to label_file.h as this is also used by utils/sefcontext_compile.c. sefcontext_compile.c - Update to use common process_line code. Now frees all malloc'ed memory, checked by valgrind. Major changes in V3: label_file.c - Improve error handling in process_file function. sefcontext_compile.c - Add callback to ignore validation. Fixed error paths to free/close memory, checked by valgrind. label_file.h - Revert validation check now added callback to sefcontext_compile.c. Changes in V4: label_file.c - linux/limits.h not supported on MacOS - change to limits.h. Improve error handling in process_file function. sefcontext_compile.c - linux/limits.h not spported on MacOS - change to limits.h. Improve error handling in process_file function. Changes in V5: sefcontext_compile.c - Change callbacks so this can build on Android and selinux upstream with no changes. label_android_property.c and label_internal.h - Minor formatting changes to bring into line with selinux upstream. Changes in V6: sefcontext_compile.c - Revert to V3 callback now the problem has been fixed by upstream commit e88914849490c3fc17b0e5ed67387e47f2701d3c ("libselinux: build sefcontext_compile with static libselinux") that also hid read_spec_entries function as this is not intended as a public interface for shared library users. label_android_property.c - More minor formatting changes to bring into line with selinux upstream. Changes in V7: Android.mk - Add darwin support label_file.c - In process_file set rc for getline and correct fopen to upstream. sefcontext_compile.c - In process_file set rc for getline. Change in V8: Android.mk - Remove LOCAL_CFLAGS := -Wall -Werror Changes in V9: label_file.c - Fix file labels for regexes with metachars for text file_contexts label_support.c - Fix if file_contexts not '\n' terminated If the last line of a file_contexts file is not '\n' terminated or if any line has additional isspace(3) characters at end, it gave an invalid file type error Changes in V10 Fix file labels for regexes with metachars for binary file_contexts This change adds a new entry in the binary file with the calculated prefix length that is then read when processing the file. This fix also bumps SELINUX_COMPILED_FCONTEXT_MAX_VERS. Change-Id: Ief0d3a6a5fbffc785b02d9dffb416f837000e61c
-rw-r--r--Android.mk20
-rw-r--r--src/label_android_property.c94
-rw-r--r--src/label_file.c801
-rw-r--r--src/label_file.h467
-rw-r--r--src/label_internal.h22
-rw-r--r--src/label_support.c98
-rw-r--r--utils/sefcontext_compile.c390
7 files changed, 1431 insertions, 461 deletions
diff --git a/Android.mk b/Android.mk
index 71206c1..c04327f 100644
--- a/Android.mk
+++ b/Android.mk
@@ -37,7 +37,8 @@ common_HOST_FILES := \
src/init.c \
src/label.c \
src/label_file.c \
- src/label_android_property.c
+ src/label_android_property.c \
+ src/label_support.c
common_COPY_HEADERS_TO := selinux
@@ -102,3 +103,20 @@ LOCAL_COPY_HEADERS := $(common_COPY_HEADERS)
LOCAL_WHOLE_STATIC_LIBRARIES := libpcre
LOCAL_C_INCLUDES := external/pcre
include $(BUILD_HOST_SHARED_LIBRARY)
+
+#################################
+include $(CLEAR_VARS)
+LOCAL_CFLAGS := -DHOST
+
+ifeq ($(HOST_OS),darwin)
+LOCAL_CFLAGS += -DDARWIN
+endif
+
+LOCAL_MODULE := sefcontext_compile
+LOCAL_MODULE_TAGS := eng
+LOCAL_C_INCLUDES := ../src/label_file.h
+LOCAL_SRC_FILES := utils/sefcontext_compile.c
+LOCAL_STATIC_LIBRARIES := libselinux
+LOCAL_WHOLE_STATIC_LIBRARIES := libpcre
+LOCAL_C_INCLUDES := external/pcre
+include $(BUILD_HOST_EXECUTABLE)
diff --git a/src/label_android_property.c b/src/label_android_property.c
index 05e801c..4af9896 100644
--- a/src/label_android_property.c
+++ b/src/label_android_property.c
@@ -1,5 +1,5 @@
/*
- * Property Service contexts backend for labeling Android
+ * Property Service contexts backend for labeling Android
* property keys
*/
@@ -15,8 +15,8 @@
/* A property security context specification. */
typedef struct spec {
- struct selabel_lookup_rec lr; /* holds contexts for lookup result */
- char *property_key; /* property key string */
+ struct selabel_lookup_rec lr; /* holds contexts for lookup result */
+ char *property_key; /* property key string */
} spec_t;
/* Our stored configuration */
@@ -26,16 +26,16 @@ struct saved_data {
* prefix match
*/
spec_t *spec_arr;
- unsigned int nspec; /* total number of specifications */
+ unsigned int nspec; /* total number of specifications */
};
static int cmp(const void *A, const void *B)
{
const struct spec *sp1 = A, *sp2 = B;
- if (strncmp(sp1->property_key,"*",1) == 0)
+ if (strncmp(sp1->property_key, "*", 1) == 0)
return 1;
- if (strncmp(sp2->property_key,"*",1) == 0)
+ if (strncmp(sp2->property_key, "*", 1) == 0)
return -1;
size_t L1 = strlen(sp1->property_key);
@@ -56,12 +56,12 @@ static int nodups_specs(struct saved_data *data, const char *path)
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].property_key, curr_spec->property_key))) {
+ if (!strcmp(spec_arr[jj].property_key,
+ curr_spec->property_key)) {
rc = -1;
errno = EINVAL;
- if (strcmp
- (spec_arr[jj].lr.ctx_raw,
- curr_spec->lr.ctx_raw)) {
+ if (strcmp(spec_arr[jj].lr.ctx_raw,
+ curr_spec->lr.ctx_raw)) {
selinux_log
(SELINUX_ERROR,
"%s: Multiple different specifications for %s (%s and %s).\n",
@@ -81,60 +81,55 @@ static int nodups_specs(struct saved_data *data, const char *path)
}
static int process_line(struct selabel_handle *rec,
- const char *path, char *line_buf,
+ const char *path, char *line_buf,
int pass, unsigned lineno)
{
- int items, len;
- char buf1[BUFSIZ], buf2[BUFSIZ];
- char *buf_p, *prop = buf1, *context = buf2;
+ int items;
+ char *prop = NULL, *context = NULL;
struct saved_data *data = (struct saved_data *)rec->data;
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, "%255s %255s", prop, context);
+ items = read_spec_entries(line_buf, 2, &prop, &context);
+ if (items <= 0)
+ return items;
if (items != 2) {
selinux_log(SELINUX_WARNING,
- "%s: line %d is missing fields, skipping\n", path,
+ "%s: line %u is missing fields, skipping\n", path,
lineno);
+ free(prop);
return 0;
}
- if (pass == 1) {
+ if (pass == 0) {
+ free(prop);
+ free(context);
+ } else if (pass == 1) {
/* On the second pass, process and store the specification in spec. */
- spec_arr[nspec].property_key = strdup(prop);
+ spec_arr[nspec].property_key = prop;
if (!spec_arr[nspec].property_key) {
selinux_log(SELINUX_WARNING,
- "%s: out of memory at line %d on prop %s\n",
+ "%s: out of memory at line %u on prop %s\n",
path, lineno, prop);
- return -1;
-
+ return -1;
}
- spec_arr[nspec].lr.ctx_raw = strdup(context);
+ spec_arr[nspec].lr.ctx_raw = context;
if (!spec_arr[nspec].lr.ctx_raw) {
selinux_log(SELINUX_WARNING,
- "%s: out of memory at line %d on context %s\n",
+ "%s: out of memory at line %u on context %s\n",
path, lineno, context);
- return -1;
+ return -1;
}
if (rec->validating) {
- if (selabel_validate(rec, &spec_arr[nspec].lr) < 0) {
- selinux_log(SELINUX_WARNING,
- "%s: line %d has invalid context %s\n",
+ if (selabel_validate(rec, &spec_arr[nspec].lr) < 0) {
+ selinux_log(SELINUX_WARNING,
+ "%s: line %u has invalid context %s\n",
path, lineno, spec_arr[nspec].lr.ctx_raw);
}
}
- }
+ }
data->nspec = ++nspec;
return 0;
@@ -174,30 +169,29 @@ static int init(struct selabel_handle *rec, const struct selinux_opt *opts,
/*
* Two passes of the specification file. First is to get the size.
- * After the first pass, the spec array is malloced to the appropriate
- * size. Second pass is to populate the spec array and check for
+ * After the first pass, the spec array is malloced to the appropriate
+ * size. Second pass is to populate the spec array and check for
* dups.
*/
maxnspec = UINT_MAX / sizeof(spec_t);
for (pass = 0; pass < 2; pass++) {
data->nspec = 0;
- while (fgets(line_buf, sizeof line_buf - 1, fp)
+ while (fgets(line_buf, sizeof(line_buf) - 1, fp)
&& data->nspec < maxnspec) {
- if (process_line(rec, path, line_buf, pass, ++lineno) != 0) {
+ if (process_line(rec, path, line_buf, pass, ++lineno)
+ != 0)
goto finish;
- }
}
if (pass == 1) {
status = nodups_specs(data, path);
-
+
if (status)
goto finish;
}
if (pass == 0) {
-
if (data->nspec == 0) {
status = 0;
goto finish;
@@ -207,7 +201,7 @@ static int init(struct selabel_handle *rec, const struct selinux_opt *opts,
malloc(sizeof(spec_t) * data->nspec)))
goto finish;
- memset(data->spec_arr, 0, sizeof(spec_t)*data->nspec);
+ memset(data->spec_arr, 0, sizeof(spec_t) * data->nspec);
maxnspec = data->nspec;
rewind(fp);
}
@@ -239,12 +233,12 @@ static void closef(struct selabel_handle *rec)
if (data->spec_arr)
free(data->spec_arr);
-
+
free(data);
}
-static struct selabel_lookup_rec *lookup(struct selabel_handle *rec,
- const char *key,
+static struct selabel_lookup_rec *lookup(struct selabel_handle *rec,
+ const char *key,
int __attribute__((unused)) type)
{
struct saved_data *data = (struct saved_data *)rec->data;
@@ -258,8 +252,8 @@ static struct selabel_lookup_rec *lookup(struct selabel_handle *rec,
}
for (i = 0; i < data->nspec; i++) {
- if (strncmp(spec_arr[i].property_key, key,
- strlen(spec_arr[i].property_key)) == 0) {
+ if (strncmp(spec_arr[i].property_key, key,
+ strlen(spec_arr[i].property_key)) == 0) {
break;
}
if (strncmp(spec_arr[i].property_key, "*", 1) == 0)
diff --git a/src/label_file.c b/src/label_file.c
index c860016..54931d7 100644
--- a/src/label_file.c
+++ b/src/label_file.c
@@ -2,6 +2,7 @@
* File contexts backend for labeling system
*
* Author : Eamon Walsh <ewalsh@tycho.nsa.gov>
+ * Author : Stephen Smalley <sds@tycho.nsa.gov>
*/
#include <fcntl.h>
@@ -11,73 +12,21 @@
#include <ctype.h>
#include <errno.h>
#include <limits.h>
+#include <stdint.h>
+#include <pcre.h>
+#include <unistd.h>
+#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
-#include <unistd.h>
+
#include "callbacks.h"
#include "label_internal.h"
-#include <pcre.h>
+#include "label_file.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 */
- pcre *regex; /* compiled regular expression */
- pcre_extra *sd; /* pcre study result */
- char regcomp; /* regex_str has been compiled to regex */
- 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 */
- size_t prefix_len; /* length of fixed path prefix */
-} 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;
- unsigned int ncomp;
-
- /*
- * 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)
{
@@ -88,41 +37,6 @@ static int get_stem_from_file_name(const char *const buf)
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). */
-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))
- return i;
- }
- if (data->alloc_stems == num) {
- stem_t *tmp_arr;
- data->alloc_stems = data->alloc_stems * 2 + 16;
- tmp_arr = (stem_t *) 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 = (char *) 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. */
@@ -155,15 +69,14 @@ static int nodups_specs(struct saved_data *data, const char *path)
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))
+ 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)) {
rc = -1;
errno = EINVAL;
- if (strcmp
- (spec_arr[jj].lr.ctx_raw,
- curr_spec->lr.ctx_raw)) {
+ if (strcmp(spec_arr[jj].lr.ctx_raw,
+ curr_spec->lr.ctx_raw)) {
selinux_log
(SELINUX_ERROR,
"%s: Multiple different specifications for %s (%s and %s).\n",
@@ -182,218 +95,357 @@ static int nodups_specs(struct saved_data *data, const char *path)
return rc;
}
-/* Determine if the regular expression specification has any meta characters. */
-static void spec_hasMetaChars(struct spec *spec)
+static int load_mmap(struct selabel_handle *rec, const char *path,
+ struct stat *sb)
{
- char *c;
+ struct saved_data *data = (struct saved_data *)rec->data;
+ char mmap_path[PATH_MAX + 1];
+ int mmapfd;
+ int rc;
+ struct stat mmap_stat;
+ char *addr, *str_buf;
size_t len;
- char *end;
-
- c = spec->regex_str;
- len = strlen(spec->regex_str);
- end = c + len;
-
- spec->hasMetaChars = 0;
- spec->prefix_len = len;
-
- /* 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;
- spec->prefix_len = c - spec->regex_str;
- return;
- case '\\': /* skip the next character */
- c++;
- break;
- default:
- break;
+ int *stem_map;
+ struct mmap_area *mmap_area;
+ uint32_t i, magic, version;
+ uint32_t entry_len, stem_map_len, regex_array_len;
- }
- c++;
- }
- return;
-}
+ rc = snprintf(mmap_path, sizeof(mmap_path), "%s.bin", path);
+ if (rc >= (int)sizeof(mmap_path))
+ return -1;
-static int compile_regex(struct saved_data *data, spec_t *spec, const char **errbuf)
-{
- const char *tmperrbuf;
- char *reg_buf, *anchored_regex, *cp;
- stem_t *stem_arr = data->stem_arr;
- size_t len;
- int erroff;
+ mmapfd = open(mmap_path, O_RDONLY | O_CLOEXEC);
+ if (mmapfd < 0)
+ return -1;
- if (spec->regcomp)
- return 0; /* already done */
+ rc = fstat(mmapfd, &mmap_stat);
+ if (rc < 0) {
+ close(mmapfd);
+ return -1;
+ }
- data->ncomp++; /* how many compiled regexes required */
+ /* if mmap is old, ignore it */
+ if (mmap_stat.st_mtime < sb->st_mtime) {
+ close(mmapfd);
+ return -1;
+ }
- /* Skip the fixed stem. */
- reg_buf = spec->regex_str;
- if (spec->stem_id >= 0)
- reg_buf += stem_arr[spec->stem_id].len;
+ /* ok, read it in... */
+ len = mmap_stat.st_size;
+ len += (sysconf(_SC_PAGE_SIZE) - 1);
+ len &= ~(sysconf(_SC_PAGE_SIZE) - 1);
- /* Anchor the regular expression. */
- len = strlen(reg_buf);
- cp = anchored_regex = (char *) malloc(len + 3);
- if (!anchored_regex)
+ mmap_area = malloc(sizeof(*mmap_area));
+ if (!mmap_area) {
+ close(mmapfd);
return -1;
- /* Create ^...$ regexp. */
- *cp++ = '^';
- memcpy(cp, reg_buf, len);
- cp += len;
- *cp++ = '$';
- *cp = '\0';
-
- /* Compile the regular expression. */
- spec->regex = pcre_compile(anchored_regex, PCRE_DOTALL, &tmperrbuf, &erroff, NULL);
- free(anchored_regex);
- if (!spec->regex) {
- if (errbuf)
- *errbuf=tmperrbuf;
+ }
+
+ addr = mmap(NULL, len, PROT_READ, MAP_PRIVATE, mmapfd, 0);
+ close(mmapfd);
+ if (addr == MAP_FAILED) {
+ free(mmap_area);
+ perror("mmap");
return -1;
}
- spec->sd = pcre_study(spec->regex, 0, &tmperrbuf);
- if (!spec->sd && tmperrbuf) {
- if (errbuf)
- *errbuf=tmperrbuf;
+ /* save where we mmap'd the file to cleanup on close() */
+ mmap_area->addr = addr;
+ mmap_area->len = len;
+ mmap_area->next = data->mmap_areas;
+ data->mmap_areas = mmap_area;
+
+ /* check if this looks like an fcontext file */
+ rc = next_entry(&magic, mmap_area, sizeof(uint32_t));
+ if (rc < 0 || magic != SELINUX_MAGIC_COMPILED_FCONTEXT)
+ return -1;
+
+ /* check if this version is higher than we understand */
+ rc = next_entry(&version, mmap_area, sizeof(uint32_t));
+ if (rc < 0 || version > SELINUX_COMPILED_FCONTEXT_MAX_VERS)
return -1;
+
+ if (version >= SELINUX_COMPILED_FCONTEXT_PCRE_VERS) {
+ len = strlen(pcre_version());
+
+ rc = next_entry(&entry_len, mmap_area, sizeof(uint32_t));
+ if (rc < 0)
+ return -1;
+
+ /* Check version lengths */
+ if (len != entry_len)
+ return -1;
+
+ /* Check if pcre version mismatch */
+ str_buf = malloc(entry_len + 1);
+ if (!str_buf)
+ return -1;
+
+ rc = next_entry(str_buf, mmap_area, entry_len);
+ if (rc < 0) {
+ free(str_buf);
+ return -1;
+ }
+
+ str_buf[entry_len] = '\0';
+ if ((strcmp(str_buf, pcre_version()) != 0)) {
+ free(str_buf);
+ return -1;
+ }
+ free(str_buf);
}
- /* Done. */
- spec->regcomp = 1;
+ /* allocate the stems_data array */
+ rc = next_entry(&stem_map_len, mmap_area, sizeof(uint32_t));
+ if (rc < 0 || !stem_map_len)
+ return -1;
- return 0;
-}
+ /*
+ * map indexed by the stem # in the mmap file and contains the stem
+ * number in the data stem_arr
+ */
+ stem_map = calloc(stem_map_len, sizeof(*stem_map));
+ if (!stem_map)
+ return -1;
+ for (i = 0; i < stem_map_len; i++) {
+ char *buf;
+ uint32_t stem_len;
+ int newid;
-static int process_line(struct selabel_handle *rec,
- const char *path, const char *prefix,
- char *line_buf, int pass, unsigned lineno)
-{
- int items, len;
- char buf1[BUFSIZ], buf2[BUFSIZ], buf3[BUFSIZ];
- char *buf_p, *regex = buf1, *type = buf2, *context = buf3;
- struct saved_data *data = (struct saved_data *)rec->data;
- 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, "%255s %255s %255s", regex, 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. */
- context = type;
- type = NULL;
+ /* the length does not inlude the nul */
+ rc = next_entry(&stem_len, mmap_area, sizeof(uint32_t));
+ if (rc < 0 || !stem_len) {
+ rc = -1;
+ goto err;
+ }
+
+ /* Check for stem_len wrap around. */
+ if (stem_len < UINT32_MAX) {
+ buf = (char *)mmap_area->addr;
+ /* Check if over-run before null check. */
+ rc = next_entry(NULL, mmap_area, (stem_len + 1));
+ if (rc < 0)
+ goto err;
+
+ if (buf[stem_len] != '\0') {
+ rc = -1;
+ goto err;
+ }
+ } else {
+ rc = -1;
+ goto err;
+ }
+
+ /* store the mapping between old and new */
+ newid = find_stem(data, buf, stem_len);
+ if (newid < 0) {
+ newid = store_stem(data, buf, stem_len);
+ if (newid < 0) {
+ rc = newid;
+ goto err;
+ }
+ data->stem_arr[newid].from_mmap = 1;
+ }
+ stem_map[i] = newid;
}
- len = get_stem_from_spec(regex);
- if (len && prefix && strncmp(prefix, regex, len)) {
- /* Stem of regex does not match requested prefix, discard. */
- return 0;
+ /* allocate the regex array */
+ rc = next_entry(&regex_array_len, mmap_area, sizeof(uint32_t));
+ if (rc < 0 || !regex_array_len) {
+ rc = -1;
+ goto err;
}
- if (pass == 1) {
- /* On the second pass, process and store the specification in spec. */
- const char *errbuf = NULL;
- spec_arr[nspec].stem_id = find_stem_from_spec(data, regex);
- spec_arr[nspec].regex_str = strdup(regex);
- if (!spec_arr[nspec].regex_str) {
- selinux_log(SELINUX_WARNING,
- "%s: out of memory at line %d on regex %s\n",
- path, lineno, regex);
- return -1;
+ for (i = 0; i < regex_array_len; i++) {
+ struct spec *spec;
+ int32_t stem_id, meta_chars;
+ uint32_t mode = 0, prefix_len = 0;
+ rc = grow_specs(data);
+ if (rc < 0)
+ goto err;
+
+ spec = &data->spec_arr[data->nspec];
+ spec->from_mmap = 1;
+ spec->regcomp = 1;
+
+ /* Process context */
+ rc = next_entry(&entry_len, mmap_area, sizeof(uint32_t));
+ if (rc < 0 || !entry_len) {
+ rc = -1;
+ goto err;
}
- if (rec->validating && compile_regex(data, &spec_arr[nspec], &errbuf)) {
- selinux_log(SELINUX_WARNING,
- "%s: line %d has invalid regex %s: %s\n",
- path, lineno, regex,
- (errbuf ? errbuf : "out of memory"));
+
+ str_buf = malloc(entry_len);
+ if (!str_buf) {
+ rc = -1;
+ goto err;
}
+ rc = next_entry(str_buf, mmap_area, entry_len);
+ if (rc < 0)
+ goto err;
+
+ if (str_buf[entry_len - 1] != '\0') {
+ free(str_buf);
+ rc = -1;
+ goto err;
+ }
+ spec->lr.ctx_raw = str_buf;
- /* Convert the type string to a mode format */
- spec_arr[nspec].mode = 0;
- if (!type)
- goto skip_type;
- spec_arr[nspec].type_str = strdup(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;
+ /* Process regex string */
+ rc = next_entry(&entry_len, mmap_area, sizeof(uint32_t));
+ if (rc < 0 || !entry_len) {
+ rc = -1;
+ goto err;
}
- 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;
+
+ spec->regex_str = (char *)mmap_area->addr;
+ rc = next_entry(NULL, mmap_area, entry_len);
+ if (rc < 0)
+ goto err;
+
+ if (spec->regex_str[entry_len - 1] != '\0') {
+ rc = -1;
+ goto err;
}
- skip_type:
- spec_arr[nspec].lr.ctx_raw = strdup(context);
+ /* Process mode */
+ if (version >= SELINUX_COMPILED_FCONTEXT_MODE)
+ rc = next_entry(&mode, mmap_area, sizeof(uint32_t));
+ else
+ rc = next_entry(&mode, mmap_area, sizeof(mode_t));
+ if (rc < 0)
+ goto err;
+
+ spec->mode = mode;
+
+ /* map the stem id from the mmap file to the data->stem_arr */
+ rc = next_entry(&stem_id, mmap_area, sizeof(int32_t));
+ if (rc < 0)
+ goto err;
+
+ if (stem_id < 0 || stem_id >= (int32_t)stem_map_len)
+ spec->stem_id = -1;
+ else
+ spec->stem_id = stem_map[stem_id];
+
+ /* retrieve the hasMetaChars bit */
+ rc = next_entry(&meta_chars, mmap_area, sizeof(uint32_t));
+ if (rc < 0)
+ goto err;
+
+ spec->hasMetaChars = meta_chars;
+ /* and prefix length for use by selabel_lookup_best_match */
+ if (version >= SELINUX_COMPILED_FCONTEXT_PREFIX_LEN) {
+ rc = next_entry(&prefix_len, mmap_area,
+ sizeof(uint32_t));
+ if (rc < 0)
+ goto err;
+
+ spec->prefix_len = prefix_len;
+ }
+
+ /* Process regex and study_data entries */
+ rc = next_entry(&entry_len, mmap_area, sizeof(uint32_t));
+ if (rc < 0 || !entry_len) {
+ rc = -1;
+ goto err;
+ }
+ spec->regex = (pcre *)mmap_area->addr;
+ rc = next_entry(NULL, mmap_area, entry_len);
+ if (rc < 0)
+ goto err;
+
+ /* Check that regex lengths match. pcre_fullinfo()
+ * also validates its magic number. */
+ rc = pcre_fullinfo(spec->regex, NULL, PCRE_INFO_SIZE, &len);
+ if (rc < 0 || len != entry_len) {
+ rc = -1;
+ goto err;
+ }
- if (strcmp(context, "<<none>>") && rec->validating) {
- if (selabel_validate(rec, &spec_arr[nspec].lr) < 0) {
- selinux_log(SELINUX_WARNING,
- "%s: line %d has invalid context %s\n",
- path, lineno, spec_arr[nspec].lr.ctx_raw);
- }
+ rc = next_entry(&entry_len, mmap_area, sizeof(uint32_t));
+ if (rc < 0 || !entry_len) {
+ rc = -1;
+ goto err;
+ }
+ spec->lsd.study_data = (void *)mmap_area->addr;
+ spec->lsd.flags |= PCRE_EXTRA_STUDY_DATA;
+ rc = next_entry(NULL, mmap_area, entry_len);
+ if (rc < 0)
+ goto err;
+
+ /* Check that study data lengths match. */
+ rc = pcre_fullinfo(spec->regex, &spec->lsd,
+ PCRE_INFO_STUDYSIZE, &len);
+ if (rc < 0 || len != entry_len) {
+ rc = -1;
+ goto err;
}
- /* Determine if specification has
- * any meta characters in the RE */
- spec_hasMetaChars(&spec_arr[nspec]);
+ data->nspec++;
}
+ /* win */
+ rc = 0;
+err:
+ free(stem_map);
- data->nspec = ++nspec;
- return 0;
+ return rc;
+}
+
+static int process_file(const char *path, const char *suffix,
+ struct selabel_handle *rec, const char *prefix)
+{
+ FILE *fp;
+ struct stat sb;
+ unsigned int lineno;
+ size_t line_len = 0;
+ char *line_buf = NULL;
+ int rc;
+ char stack_path[PATH_MAX + 1];
+
+ /* append the path suffix if we have one */
+ if (suffix) {
+ rc = snprintf(stack_path, sizeof(stack_path),
+ "%s.%s", path, suffix);
+ if (rc >= (int)sizeof(stack_path)) {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+ path = stack_path;
+ }
+
+ /* Open the specification file. */
+ if ((fp = fopen(path, "r")) == NULL)
+ return -1;
+
+ if (fstat(fileno(fp), &sb) < 0)
+ return -1;
+ if (!S_ISREG(sb.st_mode)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ rc = load_mmap(rec, path, &sb);
+ if (rc == 0)
+ goto out;
+
+ /*
+ * Then do detailed validation of the input and fill the spec array
+ */
+ lineno = 0;
+ rc = 0;
+ while (getline(&line_buf, &line_len, fp) > 0) {
+ rc = process_line(rec, path, prefix, line_buf, ++lineno);
+ if (rc)
+ goto out;
+ }
+
+out:
+ free(line_buf);
+ fclose(fp);
+ return rc;
}
static int init(struct selabel_handle *rec, const struct selinux_opt *opts,
@@ -402,16 +454,7 @@ static int init(struct selabel_handle *rec, const struct selinux_opt *opts,
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[BUFSIZ];
- unsigned int lineno, pass, i, j, maxnspec;
- spec_t *spec_copy = NULL;
int status = -1, baseonly = 0;
- struct stat sb;
/* Process arguments */
while (n--)
@@ -427,113 +470,36 @@ static int init(struct selabel_handle *rec, const struct selinux_opt *opts,
break;
}
- /* Open the specification file. */
- if ((fp = fopen(path, "r")) == NULL)
- return -1;
+ rec->spec_file = strdup(path);
- if (fstat(fileno(fp), &sb) < 0)
- return -1;
- if (!S_ISREG(sb.st_mode)) {
- errno = EINVAL;
- return -1;
+ /*
+ * The do detailed validation of the input and fill the spec array
+ */
+ status = process_file(path, NULL, rec, prefix);
+ if (status)
+ goto finish;
+
+ if (rec->validating) {
+ status = nodups_specs(data, path);
+ if (status)
+ goto finish;
}
if (!baseonly) {
- snprintf(homedir_path, sizeof(homedir_path), "%s.homedirs",
- path);
- homedirfp = fopen(homedir_path, "r");
+ status = process_file(path, "homedirs", rec, prefix);
+ if (status && errno != ENOENT)
+ goto finish;
- snprintf(local_path, sizeof(local_path), "%s.local", path);
- localfp = fopen(local_path, "r");
+ status = process_file(path, "local", rec, prefix);
+ if (status && errno != ENOENT)
+ goto finish;
}
- /*
- * 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;
- data->ncomp = 0;
- while (fgets(line_buf, sizeof line_buf - 1, fp)
- && data->nspec < maxnspec) {
- if (process_line(rec, path, prefix, line_buf,
- pass, ++lineno) != 0)
- goto finish;
- }
- if (pass == 1) {
- status = nodups_specs(data, path);
- if (status)
- goto finish;
- }
- lineno = 0;
- if (homedirfp)
- while (fgets(line_buf, sizeof line_buf - 1, homedirfp)
- && data->nspec < maxnspec) {
- if (process_line
- (rec, homedir_path, prefix,
- line_buf, pass, ++lineno) != 0)
- goto finish;
- }
-
- lineno = 0;
- if (localfp)
- while (fgets(line_buf, sizeof line_buf - 1, localfp)
- && data->nspec < maxnspec) {
- if (process_line
- (rec, local_path, prefix, line_buf,
- pass, ++lineno) != 0)
- goto finish;
- }
+ status = sort_specs(data);
- if (pass == 0) {
- if (data->nspec == 0) {
- status = 0;
- goto finish;
- }
- if (NULL == (data->spec_arr =
- (spec_t *) 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);
- }
- }
-
- /* Move exact pathname specifications to the end. */
- spec_copy = (spec_t *) 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;
-
- status = 0;
finish:
- fclose(fp);
- if (data->spec_arr != spec_copy)
+ if (status)
free(data->spec_arr);
- if (homedirfp)
- fclose(homedirfp);
- if (localfp)
- fclose(localfp);
return status;
}
@@ -543,16 +509,19 @@ finish:
static void closef(struct selabel_handle *rec)
{
struct saved_data *data = (struct saved_data *)rec->data;
+ struct mmap_area *area, *last_area;
struct spec *spec;
struct stem *stem;
unsigned int i;
for (i = 0; i < data->nspec; i++) {
spec = &data->spec_arr[i];
+ free(spec->lr.ctx_trans);
+ free(spec->lr.ctx_raw);
+ if (spec->from_mmap)
+ continue;
free(spec->regex_str);
free(spec->type_str);
- free(spec->lr.ctx_raw);
- free(spec->lr.ctx_trans);
if (spec->regcomp) {
pcre_free(spec->regex);
pcre_free_study(spec->sd);
@@ -561,6 +530,8 @@ static void closef(struct selabel_handle *rec)
for (i = 0; i < (unsigned int)data->num_stems; i++) {
stem = &data->stem_arr[i];
+ if (stem->from_mmap)
+ continue;
free(stem->buf);
}
@@ -568,21 +539,28 @@ static void closef(struct selabel_handle *rec)
free(data->spec_arr);
if (data->stem_arr)
free(data->stem_arr);
-
+
+ area = data->mmap_areas;
+ while (area) {
+ munmap(area->addr, area->len);
+ last_area = area;
+ area = area->next;
+ free(last_area);
+ }
free(data);
}
-static spec_t *lookup_common(struct selabel_handle *rec,
- const char *key,
- int type,
- bool partial)
+static struct spec *lookup_common(struct selabel_handle *rec,
+ const char *key,
+ int type,
+ bool partial)
{
struct saved_data *data = (struct saved_data *)rec->data;
- spec_t *spec_arr = data->spec_arr;
+ struct spec *spec_arr = data->spec_arr;
int i, rc, file_stem, pcre_options = 0;
mode_t mode = (mode_t)type;
const char *buf;
- spec_t *ret = NULL;
+ struct spec *ret = NULL;
char *clean_key = NULL;
const char *prev_slash, *next_slash;
unsigned int sofar = 0;
@@ -615,34 +593,40 @@ static spec_t *lookup_common(struct selabel_handle *rec,
if (partial)
pcre_options |= PCRE_PARTIAL_SOFT;
- /*
+ /*
* Check for matching specifications in reverse order, so that
* the last matching specification is used.
*/
for (i = data->nspec - 1; i >= 0; i--) {
+ struct spec *spec = &spec_arr[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 (compile_regex(data, &spec_arr[i], NULL) < 0)
+ if ((spec->stem_id == -1 || spec->stem_id == file_stem) &&
+ (!mode || !spec->mode || mode == spec->mode)) {
+ if (compile_regex(data, spec, NULL) < 0)
goto finish;
- if (spec_arr[i].stem_id == -1)
- rc = pcre_exec(spec_arr[i].regex, spec_arr[i].sd, key, strlen(key), 0, pcre_options, NULL, 0);
+ if (spec->stem_id == -1)
+ rc = pcre_exec(spec->regex,
+ get_pcre_extra(spec),
+ key, strlen(key), 0,
+ pcre_options, NULL, 0);
else
- rc = pcre_exec(spec_arr[i].regex, spec_arr[i].sd, buf, strlen(buf), 0, pcre_options, NULL, 0);
-
+ rc = pcre_exec(spec->regex,
+ get_pcre_extra(spec),
+ buf, strlen(buf), 0,
+ pcre_options, NULL, 0);
if (rc == 0) {
- spec_arr[i].matches++;
+ spec->matches++;
break;
} else if (partial && rc == PCRE_ERROR_PARTIAL)
break;
if (rc == PCRE_ERROR_NOMATCH)
continue;
+
+ errno = ENOENT;
/* else it's an error */
goto finish;
}
@@ -654,6 +638,7 @@ static spec_t *lookup_common(struct selabel_handle *rec,
goto finish;
}
+ errno = 0;
ret = &spec_arr[i];
finish:
@@ -664,7 +649,8 @@ finish:
static struct selabel_lookup_rec *lookup(struct selabel_handle *rec,
const char *key, int type)
{
- spec_t *spec;
+ struct spec *spec;
+
spec = lookup_common(rec, key, type, false);
if (spec)
return &spec->lr;
@@ -683,7 +669,7 @@ static struct selabel_lookup_rec *lookup_best_match(struct selabel_handle *rec,
{
size_t n, i;
int best = -1;
- spec_t **specs;
+ struct spec **specs;
size_t prefix_len = 0;
struct selabel_lookup_rec *lr = NULL;
@@ -693,7 +679,7 @@ static struct selabel_lookup_rec *lookup_best_match(struct selabel_handle *rec,
for (n = 0; aliases[n]; n++)
;
- specs = calloc(n+1, sizeof(spec_t *));
+ specs = calloc(n+1, sizeof(struct spec *));
if (!specs)
return NULL;
specs[0] = lookup_common(rec, key, type, false);
@@ -724,6 +710,8 @@ static struct selabel_lookup_rec *lookup_best_match(struct selabel_handle *rec,
if (best >= 0) {
/* longest fixed prefix match on key or alias */
lr = &specs[best]->lr;
+ } else {
+ errno = ENOENT;
}
out:
@@ -731,11 +719,12 @@ out:
return lr;
}
+
static void stats(struct selabel_handle *rec)
{
struct saved_data *data = (struct saved_data *)rec->data;
unsigned int i, nspec = data->nspec;
- spec_t *spec_arr = data->spec_arr;
+ struct spec *spec_arr = data->spec_arr;
for (i = 0; i < nspec; i++) {
if (spec_arr[i].matches == 0) {
diff --git a/src/label_file.h b/src/label_file.h
new file mode 100644
index 0000000..3f19394
--- /dev/null
+++ b/src/label_file.h
@@ -0,0 +1,467 @@
+#ifndef _SELABEL_FILE_H_
+#define _SELABEL_FILE_H_
+
+#include <sys/stat.h>
+
+#include "callbacks.h"
+#include "label_internal.h"
+
+#define SELINUX_MAGIC_COMPILED_FCONTEXT 0xf97cff8a
+
+/* Version specific changes */
+#define SELINUX_COMPILED_FCONTEXT_NOPCRE_VERS 1
+#define SELINUX_COMPILED_FCONTEXT_PCRE_VERS 2
+#define SELINUX_COMPILED_FCONTEXT_MODE 3
+#define SELINUX_COMPILED_FCONTEXT_PREFIX_LEN 4
+
+#define SELINUX_COMPILED_FCONTEXT_MAX_VERS SELINUX_COMPILED_FCONTEXT_PREFIX_LEN
+
+/* Prior to version 8.20, libpcre did not have pcre_free_study() */
+#if (PCRE_MAJOR < 8 || (PCRE_MAJOR == 8 && PCRE_MINOR < 20))
+#define pcre_free_study pcre_free
+#endif
+
+/* A file security context specification. */
+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 */
+ pcre *regex; /* compiled regular expression */
+ union {
+ pcre_extra *sd; /* pointer to extra compiled stuff */
+ pcre_extra lsd; /* used to hold the mmap'd version */
+ };
+ mode_t mode; /* mode format value */
+ int matches; /* number of matching pathnames */
+ int stem_id; /* indicates which stem-compression item */
+ char hasMetaChars; /* regular expression has meta-chars */
+ char regcomp; /* regex_str has been compiled to regex */
+ char from_mmap; /* this spec is from an mmap of the data */
+ size_t prefix_len; /* length of fixed path prefix */
+};
+
+/* A regular expression stem */
+struct stem {
+ char *buf;
+ int len;
+ char from_mmap;
+};
+
+/* Where we map the file in during selabel_open() */
+struct mmap_area {
+ void *addr; /* Start of area - gets incremented by next_entry() */
+ size_t len; /* Length - gets decremented by next_entry() */
+ struct mmap_area *next;
+};
+
+/* 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.
+ */
+ struct spec *spec_arr;
+ unsigned int nspec;
+ unsigned int alloc_specs;
+
+ /*
+ * The array of regular expression stems.
+ */
+ struct stem *stem_arr;
+ int num_stems;
+ int alloc_stems;
+ struct mmap_area *mmap_areas;
+};
+
+static inline pcre_extra *get_pcre_extra(struct spec *spec)
+{
+ if (spec->from_mmap)
+ return &spec->lsd;
+ else
+ return spec->sd;
+}
+
+static inline mode_t string_to_mode(char *mode)
+{
+ size_t len;
+
+ if (!mode)
+ return 0;
+ len = strlen(mode);
+ if (mode[0] != '-' || len != 2)
+ return -1;
+ switch (mode[1]) {
+ case 'b':
+ return S_IFBLK;
+ case 'c':
+ return S_IFCHR;
+ case 'd':
+ return S_IFDIR;
+ case 'p':
+ return S_IFIFO;
+ case 'l':
+ return S_IFLNK;
+ case 's':
+ return S_IFSOCK;
+ case '-':
+ return S_IFREG;
+ default:
+ return -1;
+ }
+ /* impossible to get here */
+ return 0;
+}
+
+static inline int grow_specs(struct saved_data *data)
+{
+ struct spec *specs;
+ size_t new_specs, total_specs;
+
+ if (data->nspec < data->alloc_specs)
+ return 0;
+
+ new_specs = data->nspec + 16;
+ total_specs = data->nspec + new_specs;
+
+ specs = realloc(data->spec_arr, total_specs * sizeof(*specs));
+ if (!specs) {
+ perror("realloc");
+ return -1;
+ }
+
+ /* blank the new entries */
+ memset(&specs[data->nspec], 0, new_specs * sizeof(*specs));
+
+ data->spec_arr = specs;
+ data->alloc_specs = total_specs;
+ return 0;
+}
+
+/* Determine if the regular expression specification has any meta characters. */
+static inline 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;
+ spec->prefix_len = len;
+
+ /* 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;
+ spec->prefix_len = c - spec->regex_str;
+ return;
+ case '\\': /* skip the next character */
+ c++;
+ break;
+ default:
+ break;
+
+ }
+ c++;
+ }
+}
+
+/* Move exact pathname specifications to the end. */
+static inline int sort_specs(struct saved_data *data)
+{
+ struct spec *spec_copy;
+ struct spec spec;
+ unsigned int i;
+ int front, back;
+ size_t len = sizeof(*spec_copy);
+
+ spec_copy = malloc(len * data->nspec);
+ if (!spec_copy)
+ return -1;
+
+ /* first move the exact pathnames to the back */
+ front = 0;
+ back = data->nspec - 1;
+ for (i = 0; i < data->nspec; i++) {
+ if (data->spec_arr[i].hasMetaChars)
+ memcpy(&spec_copy[front++], &data->spec_arr[i], len);
+ else
+ memcpy(&spec_copy[back--], &data->spec_arr[i], len);
+ }
+
+ /*
+ * now the exact pathnames are at the end, but they are in the reverse
+ * order. Since 'front' is now the first of the 'exact' we can run
+ * that part of the array switching the front and back element.
+ */
+ back = data->nspec - 1;
+ while (front < back) {
+ /* save the front */
+ memcpy(&spec, &spec_copy[front], len);
+ /* move the back to the front */
+ memcpy(&spec_copy[front], &spec_copy[back], len);
+ /* put the old front in the back */
+ memcpy(&spec_copy[back], &spec, len);
+ front++;
+ back--;
+ }
+
+ free(data->spec_arr);
+ data->spec_arr = spec_copy;
+
+ return 0;
+}
+
+/* Return the length of the text that can be considered the stem, returns 0
+ * if there is no identifiable stem */
+static inline 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 stemid given a string and a length
+ */
+static inline int find_stem(struct saved_data *data, const char *buf,
+ int stem_len)
+{
+ int i;
+
+ 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))
+ return i;
+ }
+
+ return -1;
+}
+
+/* returns the index of the new stored object */
+static inline int store_stem(struct saved_data *data, char *buf, int stem_len)
+{
+ int num = data->num_stems;
+
+ if (data->alloc_stems == num) {
+ struct stem *tmp_arr;
+
+ data->alloc_stems = data->alloc_stems * 2 + 16;
+ tmp_arr = realloc(data->stem_arr,
+ sizeof(*tmp_arr) * 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 = buf;
+ data->num_stems++;
+
+ return num;
+}
+
+/* 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). */
+static inline int find_stem_from_spec(struct saved_data *data, const char *buf)
+{
+ int stem_len = get_stem_from_spec(buf);
+ int stemid;
+ char *stem;
+
+ if (!stem_len)
+ return -1;
+
+ stemid = find_stem(data, buf, stem_len);
+ if (stemid >= 0)
+ return stemid;
+
+ /* not found, allocate a new one */
+ stem = strndup(buf, stem_len);
+ if (!stem)
+ return -1;
+
+ return store_stem(data, stem, stem_len);
+}
+
+/* This will always check for buffer over-runs and either read the next entry
+ * if buf != NULL or skip over the entry (as these areas are mapped in the
+ * current buffer). */
+static inline int next_entry(void *buf, struct mmap_area *fp, size_t bytes)
+{
+ if (bytes > fp->len)
+ return -1;
+
+ if (buf)
+ memcpy(buf, fp->addr, bytes);
+
+ fp->addr = (char *)fp->addr + bytes;
+ fp->len -= bytes;
+ return 0;
+}
+
+static inline int compile_regex(struct saved_data *data, struct spec *spec,
+ const char **errbuf)
+{
+ const char *tmperrbuf;
+ char *reg_buf, *anchored_regex, *cp;
+ struct stem *stem_arr = data->stem_arr;
+ size_t len;
+ int erroff;
+
+ if (spec->regcomp)
+ return 0; /* already done */
+
+ /* Skip the fixed stem. */
+ reg_buf = spec->regex_str;
+ if (spec->stem_id >= 0)
+ reg_buf += stem_arr[spec->stem_id].len;
+
+ /* Anchor the regular expression. */
+ len = strlen(reg_buf);
+ cp = anchored_regex = malloc(len + 3);
+ if (!anchored_regex)
+ return -1;
+
+ /* Create ^...$ regexp. */
+ *cp++ = '^';
+ memcpy(cp, reg_buf, len);
+ cp += len;
+ *cp++ = '$';
+ *cp = '\0';
+
+ /* Compile the regular expression. */
+ spec->regex = pcre_compile(anchored_regex, PCRE_DOTALL, &tmperrbuf,
+ &erroff, NULL);
+ free(anchored_regex);
+ if (!spec->regex) {
+ if (errbuf)
+ *errbuf = tmperrbuf;
+ return -1;
+ }
+
+ spec->sd = pcre_study(spec->regex, 0, &tmperrbuf);
+ if (!spec->sd && tmperrbuf) {
+ if (errbuf)
+ *errbuf = tmperrbuf;
+ return -1;
+ }
+
+ /* Done. */
+ spec->regcomp = 1;
+
+ return 0;
+}
+
+/* This service is used by label_file.c process_file() and
+ * utils/sefcontext_compile.c */
+static inline int process_line(struct selabel_handle *rec,
+ const char *path, const char *prefix,
+ char *line_buf, unsigned lineno)
+{
+ int items, len, rc;
+ char *regex = NULL, *type = NULL, *context = NULL;
+ struct saved_data *data = (struct saved_data *)rec->data;
+ struct spec *spec_arr;
+ unsigned int nspec = data->nspec;
+ const char *errbuf = NULL;
+
+ items = read_spec_entries(line_buf, 3, &regex, &type, &context);
+ if (items <= 0)
+ return items;
+
+ if (items < 2) {
+ selinux_log(SELINUX_WARNING,
+ "%s: line %u is missing fields, skipping\n", path,
+ lineno);
+ if (items == 1)
+ free(regex);
+ return 0;
+ } else if (items == 2) {
+ /* The type field is optional. */
+ context = type;
+ type = 0;
+ }
+
+ len = get_stem_from_spec(regex);
+ if (len && prefix && strncmp(prefix, regex, len)) {
+ /* Stem of regex does not match requested prefix, discard. */
+ free(regex);
+ free(type);
+ free(context);
+ return 0;
+ }
+
+ rc = grow_specs(data);
+ if (rc)
+ return rc;
+
+ spec_arr = data->spec_arr;
+
+ /* process and store the specification in spec. */
+ spec_arr[nspec].stem_id = find_stem_from_spec(data, regex);
+ spec_arr[nspec].regex_str = regex;
+ if (rec->validating &&
+ compile_regex(data, &spec_arr[nspec], &errbuf)) {
+ selinux_log(SELINUX_WARNING,
+ "%s: line %u has invalid regex %s: %s\n",
+ path, lineno, regex,
+ (errbuf ? errbuf : "out of memory"));
+ }
+
+ /* Convert the type string to a mode format */
+ spec_arr[nspec].type_str = type;
+ spec_arr[nspec].mode = 0;
+ if (type) {
+ mode_t mode = string_to_mode(type);
+
+ if (mode == (mode_t)-1) {
+ selinux_log(SELINUX_WARNING,
+ "%s: line %u has invalid file type %s\n",
+ path, lineno, type);
+ mode = 0;
+ }
+ spec_arr[nspec].mode = mode;
+ }
+
+ spec_arr[nspec].lr.ctx_raw = context;
+
+ /* Determine if specification has
+ * any meta characters in the RE */
+ spec_hasMetaChars(&spec_arr[nspec]);
+
+ if (strcmp(context, "<<none>>") && rec->validating) {
+ if (selabel_validate(rec, &spec_arr[nspec].lr) < 0) {
+ selinux_log(SELINUX_WARNING,
+ "%s: line %u has invalid context %s\n",
+ path, lineno, spec_arr[nspec].lr.ctx_raw);
+ }
+ }
+
+ data->nspec = ++nspec;
+
+ return 0;
+}
+
+#endif /* _SELABEL_FILE_H_ */
diff --git a/src/label_internal.h b/src/label_internal.h
index 00a9bbf..0c21e36 100644
--- a/src/label_internal.h
+++ b/src/label_internal.h
@@ -55,15 +55,23 @@ struct selabel_handle {
void (*func_close) (struct selabel_handle *h);
void (*func_stats) (struct selabel_handle *h);
bool (*func_partial_match) (struct selabel_handle *h, const char *key);
- struct selabel_lookup_rec *(*func_lookup_best_match) (struct selabel_handle *h,
- const char *key,
- const char **aliases,
- int type);
+ struct selabel_lookup_rec *(*func_lookup_best_match)
+ (struct selabel_handle *h,
+ const char *key,
+ const char **aliases,
+ int type);
/* supports backend-specific state information */
void *data;
+ /*
+ * The main spec file used. Note for file contexts the local and/or
+ * homedirs could also have been used to resolve a context.
+ */
+ char *spec_file;
+
/* substitution support */
+ struct selabel_sub *dist_subs;
struct selabel_sub *subs;
};
@@ -74,4 +82,10 @@ extern int
selabel_validate(struct selabel_handle *rec,
struct selabel_lookup_rec *contexts) hidden;
+/*
+ * The read_spec_entries function may be used to
+ * replace sscanf to read entries from spec files.
+ */
+extern int read_spec_entries(char *line_buf, int num_args, ...);
+
#endif /* _SELABEL_INTERNAL_H_ */
diff --git a/src/label_support.c b/src/label_support.c
new file mode 100644
index 0000000..b3ab8ab
--- /dev/null
+++ b/src/label_support.c
@@ -0,0 +1,98 @@
+/*
+ * This file contains helper functions for labeling support.
+ *
+ * Author : Richard Haines <richard_c_haines@btinternet.com>
+ */
+
+#include <stdlib.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <string.h>
+#include "label_internal.h"
+
+/*
+ * The read_spec_entries and read_spec_entry functions may be used to
+ * replace sscanf to read entries from spec files. The file and
+ * property services now use these.
+ */
+
+/* Read an entry from a spec file (e.g. file_contexts) */
+static inline int read_spec_entry(char **entry, char **ptr, int *len)
+{
+ *entry = NULL;
+ char *tmp_buf = NULL;
+
+ while (isspace(**ptr) && **ptr != '\0')
+ (*ptr)++;
+
+ tmp_buf = *ptr;
+ *len = 0;
+
+ while (!isspace(**ptr) && **ptr != '\0') {
+ (*ptr)++;
+ (*len)++;
+ }
+
+ if (*len) {
+ *entry = strndup(tmp_buf, *len);
+ if (!*entry)
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * line_buf - Buffer containing the spec entries .
+ * num_args - The number of spec parameter entries to process.
+ * ... - A 'char **spec_entry' for each parameter.
+ * returns - The number of items processed.
+ *
+ * This function calls read_spec_entry() to do the actual string processing.
+ */
+int hidden read_spec_entries(char *line_buf, int num_args, ...)
+{
+ char **spec_entry, *buf_p;
+ int len, rc, items, entry_len = 0;
+ va_list ap;
+
+ len = strlen(line_buf);
+ if (line_buf[len - 1] == '\n')
+ line_buf[len - 1] = '\0';
+ else
+ /* Handle case if line not \n terminated by bumping
+ * the len for the check below (as the line is NUL
+ * terminated by getline(3)) */
+ len++;
+
+ buf_p = line_buf;
+ while (isspace(*buf_p))
+ buf_p++;
+
+ /* Skip comment lines and empty lines. */
+ if (*buf_p == '#' || *buf_p == '\0')
+ return 0;
+
+ /* Process the spec file entries */
+ va_start(ap, num_args);
+
+ items = 0;
+ while (items < num_args) {
+ spec_entry = va_arg(ap, char **);
+
+ if (len - 1 == buf_p - line_buf) {
+ va_end(ap);
+ return items;
+ }
+
+ rc = read_spec_entry(spec_entry, &buf_p, &entry_len);
+ if (rc < 0) {
+ va_end(ap);
+ return rc;
+ }
+ if (entry_len)
+ items++;
+ }
+ va_end(ap);
+ return items;
+}
diff --git a/utils/sefcontext_compile.c b/utils/sefcontext_compile.c
new file mode 100644
index 0000000..4160632
--- /dev/null
+++ b/utils/sefcontext_compile.c
@@ -0,0 +1,390 @@
+#include <ctype.h>
+#include <errno.h>
+#include <pcre.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <getopt.h>
+#include <limits.h>
+#include <selinux/selinux.h>
+
+#include "../src/label_file.h"
+
+static int validate_context(char __attribute__ ((unused)) **ctx)
+{
+ return 0;
+}
+
+static int process_file(struct selabel_handle *rec, const char *filename)
+{
+ unsigned int line_num;
+ int rc;
+ char *line_buf = NULL;
+ size_t line_len = 0;
+ FILE *context_file;
+ const char *prefix = NULL;
+
+ context_file = fopen(filename, "r");
+ if (!context_file) {
+ fprintf(stderr, "Error opening %s: %s\n",
+ filename, strerror(errno));
+ return -1;
+ }
+
+ line_num = 0;
+ rc = 0;
+ while (getline(&line_buf, &line_len, context_file) > 0) {
+ rc = process_line(rec, filename, prefix, line_buf, ++line_num);
+ if (rc)
+ goto out;
+ }
+out:
+ free(line_buf);
+ fclose(context_file);
+ return rc;
+}
+
+/*
+ * File Format
+ *
+ * u32 - magic number
+ * u32 - version
+ * u32 - length of pcre version EXCLUDING nul
+ * char - pcre version string EXCLUDING nul
+ * u32 - number of stems
+ * ** Stems
+ * u32 - length of stem EXCLUDING nul
+ * char - stem char array INCLUDING nul
+ * u32 - number of regexs
+ * ** Regexes
+ * u32 - length of upcoming context INCLUDING nul
+ * char - char array of the raw context
+ * u32 - length of the upcoming regex_str
+ * char - char array of the original regex string including the stem.
+ * u32 - mode bits for >= SELINUX_COMPILED_FCONTEXT_MODE
+ * mode_t for <= SELINUX_COMPILED_FCONTEXT_PCRE_VERS
+ * s32 - stemid associated with the regex
+ * u32 - spec has meta characters
+ * u32 - The specs prefix_len if >= SELINUX_COMPILED_FCONTEXT_PREFIX_LEN
+ * u32 - data length of the pcre regex
+ * char - a bufer holding the raw pcre regex info
+ * u32 - data length of the pcre regex study daya
+ * char - a buffer holding the raw pcre regex study data
+ */
+static int write_binary_file(struct saved_data *data, int fd)
+{
+ struct spec *specs = data->spec_arr;
+ FILE *bin_file;
+ size_t len;
+ uint32_t magic = SELINUX_MAGIC_COMPILED_FCONTEXT;
+ uint32_t section_len;
+ uint32_t i;
+ int rc;
+
+ bin_file = fdopen(fd, "w");
+ if (!bin_file) {
+ perror("fopen output_file");
+ exit(EXIT_FAILURE);
+ }
+
+ /* write some magic number */
+ len = fwrite(&magic, sizeof(uint32_t), 1, bin_file);
+ if (len != 1)
+ goto err;
+
+ /* write the version */
+ section_len = SELINUX_COMPILED_FCONTEXT_MAX_VERS;
+ len = fwrite(&section_len, sizeof(uint32_t), 1, bin_file);
+ if (len != 1)
+ goto err;
+
+ /* write the pcre version */
+ section_len = strlen(pcre_version());
+ len = fwrite(&section_len, sizeof(uint32_t), 1, bin_file);
+ if (len != 1)
+ goto err;
+ len = fwrite(pcre_version(), sizeof(char), section_len, bin_file);
+ if (len != section_len)
+ goto err;
+
+ /* write the number of stems coming */
+ section_len = data->num_stems;
+ len = fwrite(&section_len, sizeof(uint32_t), 1, bin_file);
+ if (len != 1)
+ goto err;
+
+ for (i = 0; i < section_len; i++) {
+ char *stem = data->stem_arr[i].buf;
+ uint32_t stem_len = data->stem_arr[i].len;
+
+ /* write the strlen (aka no nul) */
+ len = fwrite(&stem_len, sizeof(uint32_t), 1, bin_file);
+ if (len != 1)
+ goto err;
+
+ /* include the nul in the file */
+ stem_len += 1;
+ len = fwrite(stem, sizeof(char), stem_len, bin_file);
+ if (len != stem_len)
+ goto err;
+ }
+
+ /* write the number of regexes coming */
+ section_len = data->nspec;
+ len = fwrite(&section_len, sizeof(uint32_t), 1, bin_file);
+ if (len != 1)
+ goto err;
+
+ for (i = 0; i < section_len; i++) {
+ char *context = specs[i].lr.ctx_raw;
+ char *regex_str = specs[i].regex_str;
+ mode_t mode = specs[i].mode;
+ size_t prefix_len = specs[i].prefix_len;
+ int32_t stem_id = specs[i].stem_id;
+ pcre *re = specs[i].regex;
+ pcre_extra *sd = get_pcre_extra(&specs[i]);
+ uint32_t to_write;
+ size_t size;
+
+ /* length of the context string (including nul) */
+ to_write = strlen(context) + 1;
+ len = fwrite(&to_write, sizeof(uint32_t), 1, bin_file);
+ if (len != 1)
+ goto err;
+
+ /* original context strin (including nul) */
+ len = fwrite(context, sizeof(char), to_write, bin_file);
+ if (len != to_write)
+ goto err;
+
+ /* length of the original regex string (including nul) */
+ to_write = strlen(regex_str) + 1;
+ len = fwrite(&to_write, sizeof(uint32_t), 1, bin_file);
+ if (len != 1)
+ goto err;
+
+ /* original regex string */
+ len = fwrite(regex_str, sizeof(char), to_write, bin_file);
+ if (len != to_write)
+ goto err;
+
+ /* binary F_MODE bits */
+ to_write = mode;
+ len = fwrite(&to_write, sizeof(uint32_t), 1, bin_file);
+ if (len != 1)
+ goto err;
+
+ /* stem for this regex (could be -1) */
+ len = fwrite(&stem_id, sizeof(stem_id), 1, bin_file);
+ if (len != 1)
+ goto err;
+
+ /* does this spec have a metaChar? */
+ to_write = specs[i].hasMetaChars;
+ len = fwrite(&to_write, sizeof(to_write), 1, bin_file);
+ if (len != 1)
+ goto err;
+
+ /* For SELINUX_COMPILED_FCONTEXT_PREFIX_LEN */
+ to_write = prefix_len;
+ len = fwrite(&to_write, sizeof(to_write), 1, bin_file);
+ if (len != 1)
+ goto err;
+
+ /* determine the size of the pcre data in bytes */
+ rc = pcre_fullinfo(re, NULL, PCRE_INFO_SIZE, &size);
+ if (rc < 0)
+ goto err;
+
+ /* write the number of bytes in the pcre data */
+ to_write = size;
+ len = fwrite(&to_write, sizeof(uint32_t), 1, bin_file);
+ if (len != 1)
+ goto err;
+
+ /* write the actual pcre data as a char array */
+ len = fwrite(re, 1, to_write, bin_file);
+ if (len != to_write)
+ goto err;
+
+ /* determine the size of the pcre study info */
+ rc = pcre_fullinfo(re, sd, PCRE_INFO_STUDYSIZE, &size);
+ if (rc < 0)
+ goto err;
+
+ /* write the number of bytes in the pcre study data */
+ to_write = size;
+ len = fwrite(&to_write, sizeof(uint32_t), 1, bin_file);
+ if (len != 1)
+ goto err;
+
+ /* write the actual pcre study data as a char array */
+ len = fwrite(sd->study_data, 1, to_write, bin_file);
+ if (len != to_write)
+ goto err;
+ }
+
+ rc = 0;
+out:
+ fclose(bin_file);
+ return rc;
+err:
+ rc = -1;
+ goto out;
+}
+
+static void free_specs(struct saved_data *data)
+{
+ struct spec *specs = data->spec_arr;
+ unsigned int num_entries = data->nspec;
+ unsigned int i;
+
+ for (i = 0; i < num_entries; i++) {
+ free(specs[i].lr.ctx_raw);
+ free(specs[i].lr.ctx_trans);
+ free(specs[i].regex_str);
+ free(specs[i].type_str);
+ pcre_free(specs[i].regex);
+ pcre_free_study(specs[i].sd);
+ }
+ free(specs);
+
+ num_entries = data->num_stems;
+ for (i = 0; i < num_entries; i++)
+ free(data->stem_arr[i].buf);
+ free(data->stem_arr);
+
+ memset(data, 0, sizeof(*data));
+}
+
+static void usage(const char *progname)
+{
+ fprintf(stderr,
+ "usage: %s [-o out_file] fc_file\n"
+ "Where:\n\t"
+ "-o Optional file name of the PCRE formatted binary\n\t"
+ " file to be output. If not specified the default\n\t"
+ " will be fc_file with the .bin suffix appended.\n\t"
+ "fc_file The text based file contexts file to be processed.\n",
+ progname);
+ exit(EXIT_FAILURE);
+}
+
+int main(int argc, char *argv[])
+{
+ const char *path = NULL;
+ const char *out_file = NULL;
+ char stack_path[PATH_MAX + 1];
+ char *tmp = NULL;
+ int fd, rc, opt;
+ struct stat buf;
+ struct selabel_handle *rec = NULL;
+ struct saved_data *data = NULL;
+
+ if (argc < 2)
+ usage(argv[0]);
+
+ while ((opt = getopt(argc, argv, "o:")) > 0) {
+ switch (opt) {
+ case 'o':
+ out_file = optarg;
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+
+ if (optind >= argc)
+ usage(argv[0]);
+
+ path = argv[optind];
+ if (stat(path, &buf) < 0) {
+ fprintf(stderr, "Can not stat: %s: %m\n", path);
+ exit(EXIT_FAILURE);
+ }
+
+ /* Generate dummy handle for process_line() function */
+ rec = (struct selabel_handle *)calloc(1, sizeof(*rec));
+ if (!rec) {
+ fprintf(stderr, "Failed to calloc handle\n");
+ exit(EXIT_FAILURE);
+ }
+ rec->backend = SELABEL_CTX_FILE;
+
+ /* Need to set validation on to get the bin file generated by the
+ * process_line function, however as the bin file being generated
+ * may not be related to the currently loaded policy (that it
+ * would be validated against), then set callback to ignore any
+ * validation. */
+ rec->validating = 1;
+ selinux_set_callback(SELINUX_CB_VALIDATE,
+ (union selinux_callback)&validate_context);
+
+ data = (struct saved_data *)calloc(1, sizeof(*data));
+ if (!data) {
+ fprintf(stderr, "Failed to calloc saved_data\n");
+ free(rec);
+ exit(EXIT_FAILURE);
+ }
+
+ rec->data = data;
+
+ rc = process_file(rec, path);
+ if (rc < 0)
+ goto err;
+
+ rc = sort_specs(data);
+ if (rc)
+ goto err;
+
+ if (out_file)
+ rc = snprintf(stack_path, sizeof(stack_path), "%s", out_file);
+ else
+ rc = snprintf(stack_path, sizeof(stack_path), "%s.bin", path);
+
+ if (rc < 0 || rc >= (int)sizeof(stack_path))
+ goto err;
+
+ tmp = malloc(strlen(stack_path) + 7);
+ if (!tmp)
+ goto err;
+
+ rc = sprintf(tmp, "%sXXXXXX", stack_path);
+ if (rc < 0)
+ goto err;
+
+ fd = mkstemp(tmp);
+ if (fd < 0)
+ goto err;
+
+ rc = fchmod(fd, buf.st_mode);
+ if (rc < 0) {
+ perror("fchmod failed to set permission on compiled regexs");
+ goto err_unlink;
+ }
+
+ rc = write_binary_file(data, fd);
+ if (rc < 0)
+ goto err_unlink;
+
+ rc = rename(tmp, stack_path);
+ if (rc < 0)
+ goto err_unlink;
+
+ rc = 0;
+out:
+ free_specs(data);
+ free(rec);
+ free(data);
+ free(tmp);
+ return rc;
+
+err_unlink:
+ unlink(tmp);
+err:
+ rc = -1;
+ goto out;
+}