/* Author: Karl MacMillan <kmacmillan@tresys.com> * Jason Tang <jtang@tresys.com> * Chris PeBenito <cpebenito@tresys.com> * * Copyright (C) 2004-2005 Tresys Technology, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "policydb_internal.h" #include "module_internal.h" #include <sepol/policydb/link.h> #include <sepol/policydb/expand.h> #include <sepol/policydb/module.h> #include "debug.h" #include "private.h" #include <stdio.h> #include <stdlib.h> #include <limits.h> #include <inttypes.h> #define SEPOL_PACKAGE_SECTION_FC 0xf97cff90 #define SEPOL_PACKAGE_SECTION_SEUSER 0x97cff91 #define SEPOL_PACKAGE_SECTION_USER_EXTRA 0x97cff92 #define SEPOL_PACKAGE_SECTION_NETFILTER 0x97cff93 static int policy_file_seek(struct policy_file *fp, size_t offset) { switch (fp->type) { case PF_USE_STDIO: if (offset > LONG_MAX) { errno = EFAULT; return -1; } return fseek(fp->fp, (long)offset, SEEK_SET); case PF_USE_MEMORY: if (offset > fp->size) { errno = EFAULT; return -1; } fp->data -= fp->size - fp->len; fp->data += offset; fp->len = fp->size - offset; return 0; default: return 0; } } static int policy_file_length(struct policy_file *fp, size_t *out) { long prev_offset, end_offset; int rc; switch (fp->type) { case PF_USE_STDIO: prev_offset = ftell(fp->fp); if (prev_offset < 0) return prev_offset; rc = fseek(fp->fp, 0L, SEEK_END); if (rc < 0) return rc; end_offset = ftell(fp->fp); if (end_offset < 0) return end_offset; rc = fseek(fp->fp, prev_offset, SEEK_SET); if (rc < 0) return rc; *out = end_offset; break; case PF_USE_MEMORY: *out = fp->size; break;; default: *out = 0; break; } return 0; } static int module_package_init(sepol_module_package_t * p) { memset(p, 0, sizeof(sepol_module_package_t)); if (sepol_policydb_create(&p->policy)) return -1; p->version = 1; return 0; } static int set_char(char **field, char *data, size_t len) { if (*field) { free(*field); *field = NULL; } if (len) { *field = malloc(len); if (!*field) return -1; memcpy(*field, data, len); } return 0; } int sepol_module_package_create(sepol_module_package_t ** p) { int rc; *p = calloc(1, sizeof(sepol_module_package_t)); if (!(*p)) return -1; rc = module_package_init(*p); if (rc < 0) free(*p); return rc; } hidden_def(sepol_module_package_create) /* Deallocates all memory associated with a module package, including * the pointer itself. Does nothing if p is NULL. */ void sepol_module_package_free(sepol_module_package_t * p) { if (p == NULL) return; sepol_policydb_free(p->policy); free(p->file_contexts); free(p->seusers); free(p->user_extra); free(p->netfilter_contexts); free(p); } hidden_def(sepol_module_package_free) char *sepol_module_package_get_file_contexts(sepol_module_package_t * p) { return p->file_contexts; } size_t sepol_module_package_get_file_contexts_len(sepol_module_package_t * p) { return p->file_contexts_len; } char *sepol_module_package_get_seusers(sepol_module_package_t * p) { return p->seusers; } size_t sepol_module_package_get_seusers_len(sepol_module_package_t * p) { return p->seusers_len; } char *sepol_module_package_get_user_extra(sepol_module_package_t * p) { return p->user_extra; } size_t sepol_module_package_get_user_extra_len(sepol_module_package_t * p) { return p->user_extra_len; } char *sepol_module_package_get_netfilter_contexts(sepol_module_package_t * p) { return p->netfilter_contexts; } size_t sepol_module_package_get_netfilter_contexts_len(sepol_module_package_t * p) { return p->netfilter_contexts_len; } int sepol_module_package_set_file_contexts(sepol_module_package_t * p, char *data, size_t len) { if (set_char(&p->file_contexts, data, len)) return -1; p->file_contexts_len = len; return 0; } int sepol_module_package_set_seusers(sepol_module_package_t * p, char *data, size_t len) { if (set_char(&p->seusers, data, len)) return -1; p->seusers_len = len; return 0; } int sepol_module_package_set_user_extra(sepol_module_package_t * p, char *data, size_t len) { if (set_char(&p->user_extra, data, len)) return -1; p->user_extra_len = len; return 0; } int sepol_module_package_set_netfilter_contexts(sepol_module_package_t * p, char *data, size_t len) { if (set_char(&p->netfilter_contexts, data, len)) return -1; p->netfilter_contexts_len = len; return 0; } sepol_policydb_t *sepol_module_package_get_policy(sepol_module_package_t * p) { return p->policy; } /* Append each of the file contexts from each module to the base * policy's file context. 'base_context' will be reallocated to a * larger size (and thus it is an in/out reference * variable). 'base_fc_len' is the length of base's file context; it * too is a reference variable. Return 0 on success, -1 if out of * memory. */ static int link_file_contexts(sepol_module_package_t * base, sepol_module_package_t ** modules, int num_modules) { size_t fc_len; int i; char *s; fc_len = base->file_contexts_len; for (i = 0; i < num_modules; i++) { fc_len += modules[i]->file_contexts_len; } if ((s = (char *)realloc(base->file_contexts, fc_len)) == NULL) { return -1; } base->file_contexts = s; for (i = 0; i < num_modules; i++) { memcpy(base->file_contexts + base->file_contexts_len, modules[i]->file_contexts, modules[i]->file_contexts_len); base->file_contexts_len += modules[i]->file_contexts_len; } return 0; } /* Append each of the netfilter contexts from each module to the base * policy's netfilter context. 'base_context' will be reallocated to a * larger size (and thus it is an in/out reference * variable). 'base_nc_len' is the length of base's netfilter contexts; it * too is a reference variable. Return 0 on success, -1 if out of * memory. */ static int link_netfilter_contexts(sepol_module_package_t * base, sepol_module_package_t ** modules, int num_modules) { size_t base_nc_len; int i; char *base_context; base_nc_len = base->netfilter_contexts_len; for (i = 0; i < num_modules; i++) { base_nc_len += modules[i]->netfilter_contexts_len; } if ((base_context = (char *)realloc(base->netfilter_contexts, base_nc_len)) == NULL) { return -1; } base->netfilter_contexts = base_context; for (i = 0; i < num_modules; i++) { memcpy(base->netfilter_contexts + base->netfilter_contexts_len, modules[i]->netfilter_contexts, modules[i]->netfilter_contexts_len); base->netfilter_contexts_len += modules[i]->netfilter_contexts_len; } return 0; } /* Links the module packages into the base. Returns 0 on success, -1 * if a requirement was not met, or -2 for all other errors. */ int sepol_link_packages(sepol_handle_t * handle, sepol_module_package_t * base, sepol_module_package_t ** modules, int num_modules, int verbose) { policydb_t **mod_pols = NULL; int i, retval; if ((mod_pols = calloc(num_modules, sizeof(*mod_pols))) == NULL) { ERR(handle, "Out of memory!"); return -2; } for (i = 0; i < num_modules; i++) { mod_pols[i] = &modules[i]->policy->p; } retval = link_modules(handle, &base->policy->p, mod_pols, num_modules, verbose); free(mod_pols); if (retval == -3) { return -1; } else if (retval < 0) { return -2; } if (link_file_contexts(base, modules, num_modules) == -1) { ERR(handle, "Out of memory!"); return -2; } if (link_netfilter_contexts(base, modules, num_modules) == -1) { ERR(handle, "Out of memory!"); return -2; } return 0; } /* buf must be large enough - no checks are performed */ #define _read_helper_bufsize BUFSIZ static int read_helper(char *buf, struct policy_file *file, uint32_t bytes) { uint32_t offset, nel, read_len; int rc; offset = 0; nel = bytes; while (nel) { if (nel < _read_helper_bufsize) read_len = nel; else read_len = _read_helper_bufsize; rc = next_entry(&buf[offset], file, read_len); if (rc < 0) return -1; offset += read_len; nel -= read_len; } return 0; } #define MAXSECTIONS 100 /* Get the section offsets from a package file, offsets will be malloc'd to * the appropriate size and the caller must free() them */ static int module_package_read_offsets(sepol_module_package_t * mod, struct policy_file *file, size_t ** offsets, uint32_t * sections) { uint32_t *buf = NULL, nsec; unsigned i; size_t *off = NULL; int rc; buf = malloc(sizeof(uint32_t)*3); if (!buf) { ERR(file->handle, "out of memory"); goto err; } rc = next_entry(buf, file, sizeof(uint32_t) * 3); if (rc < 0) { ERR(file->handle, "module package header truncated"); goto err; } if (le32_to_cpu(buf[0]) != SEPOL_MODULE_PACKAGE_MAGIC) { ERR(file->handle, "wrong magic number for module package: expected %#08x, got %#08x", SEPOL_MODULE_PACKAGE_MAGIC, le32_to_cpu(buf[0])); goto err; } mod->version = le32_to_cpu(buf[1]); nsec = *sections = le32_to_cpu(buf[2]); if (nsec > MAXSECTIONS) { ERR(file->handle, "too many sections (%u) in module package", nsec); goto err; } off = (size_t *) malloc((nsec + 1) * sizeof(size_t)); if (!off) { ERR(file->handle, "out of memory"); goto err; } free(buf); buf = malloc(sizeof(uint32_t) * nsec); if (!buf) { ERR(file->handle, "out of memory"); goto err; } rc = next_entry(buf, file, sizeof(uint32_t) * nsec); if (rc < 0) { ERR(file->handle, "module package offset array truncated"); goto err; } for (i = 0; i < nsec; i++) { off[i] = le32_to_cpu(buf[i]); if (i && off[i] < off[i - 1]) { ERR(file->handle, "offsets are not increasing (at %u, " "offset %zu -> %zu", i, off[i - 1], off[i]); goto err; } } rc = policy_file_length(file, &off[nsec]); if (rc < 0) goto err; if (nsec && off[nsec] < off[nsec-1]) { ERR(file->handle, "offset greater than file size (at %u, " "offset %zu -> %zu", nsec, off[nsec - 1], off[nsec]); goto err; } *offsets = off; free(buf); return 0; err: free(buf); free(off); return -1; } /* Flags for which sections have been seen during parsing of module package. */ #define SEEN_MOD 1 #define SEEN_FC 2 #define SEEN_SEUSER 4 #define SEEN_USER_EXTRA 8 #define SEEN_NETFILTER 16 int sepol_module_package_read(sepol_module_package_t * mod, struct sepol_policy_file *spf, int verbose) { struct policy_file *file = &spf->pf; uint32_t buf[1], nsec; size_t *offsets, len; int rc; unsigned i, seen = 0; if (module_package_read_offsets(mod, file, &offsets, &nsec)) return -1; /* we know the section offsets, seek to them and read in the data */ for (i = 0; i < nsec; i++) { if (policy_file_seek(file, offsets[i])) { ERR(file->handle, "error seeking to offset %zu for " "module package section %u", offsets[i], i); goto cleanup; } len = offsets[i + 1] - offsets[i]; if (len < sizeof(uint32_t)) { ERR(file->handle, "module package section %u " "has too small length %zu", i, len); goto cleanup; } /* read the magic number, so that we know which function to call */ rc = next_entry(buf, file, sizeof(uint32_t)); if (rc < 0) { ERR(file->handle, "module package section %u truncated, lacks magic number", i); goto cleanup; } switch (le32_to_cpu(buf[0])) { case SEPOL_PACKAGE_SECTION_FC: if (seen & SEEN_FC) { ERR(file->handle, "found multiple file contexts sections in module package (at section %u)", i); goto cleanup; } mod->file_contexts_len = len - sizeof(uint32_t); mod->file_contexts = (char *)malloc(mod->file_contexts_len); if (!mod->file_contexts) { ERR(file->handle, "out of memory"); goto cleanup; } if (read_helper (mod->file_contexts, file, mod->file_contexts_len)) { ERR(file->handle, "invalid file contexts section at section %u", i); free(mod->file_contexts); mod->file_contexts = NULL; goto cleanup; } seen |= SEEN_FC; break; case SEPOL_PACKAGE_SECTION_SEUSER: if (seen & SEEN_SEUSER) { ERR(file->handle, "found multiple seuser sections in module package (at section %u)", i); goto cleanup; } mod->seusers_len = len - sizeof(uint32_t); mod->seusers = (char *)malloc(mod->seusers_len); if (!mod->seusers) { ERR(file->handle, "out of memory"); goto cleanup; } if (read_helper(mod->seusers, file, mod->seusers_len)) { ERR(file->handle, "invalid seuser section at section %u", i); free(mod->seusers); mod->seusers = NULL; goto cleanup; } seen |= SEEN_SEUSER; break; case SEPOL_PACKAGE_SECTION_USER_EXTRA: if (seen & SEEN_USER_EXTRA) { ERR(file->handle, "found multiple user_extra sections in module package (at section %u)", i); goto cleanup; } mod->user_extra_len = len - sizeof(uint32_t); mod->user_extra = (char *)malloc(mod->user_extra_len); if (!mod->user_extra) { ERR(file->handle, "out of memory"); goto cleanup; } if (read_helper (mod->user_extra, file, mod->user_extra_len)) { ERR(file->handle, "invalid user_extra section at section %u", i); free(mod->user_extra); mod->user_extra = NULL; goto cleanup; } seen |= SEEN_USER_EXTRA; break; case SEPOL_PACKAGE_SECTION_NETFILTER: if (seen & SEEN_NETFILTER) { ERR(file->handle, "found multiple netfilter contexts sections in module package (at section %u)", i); goto cleanup; } mod->netfilter_contexts_len = len - sizeof(uint32_t); mod->netfilter_contexts = (char *)malloc(mod->netfilter_contexts_len); if (!mod->netfilter_contexts) { ERR(file->handle, "out of memory"); goto cleanup; } if (read_helper (mod->netfilter_contexts, file, mod->netfilter_contexts_len)) { ERR(file->handle, "invalid netfilter contexts section at section %u", i); free(mod->netfilter_contexts); mod->netfilter_contexts = NULL; goto cleanup; } seen |= SEEN_NETFILTER; break; case POLICYDB_MOD_MAGIC: if (seen & SEEN_MOD) { ERR(file->handle, "found multiple module sections in module package (at section %u)", i); goto cleanup; } /* seek back to where the magic number was */ if (policy_file_seek(file, offsets[i])) goto cleanup; rc = policydb_read(&mod->policy->p, file, verbose); if (rc < 0) { ERR(file->handle, "invalid module in module package (at section %u)", i); goto cleanup; } seen |= SEEN_MOD; break; default: /* unknown section, ignore */ ERR(file->handle, "unknown magic number at section %u, offset: %zx, number: %x ", i, offsets[i], le32_to_cpu(buf[0])); break; } } if ((seen & SEEN_MOD) == 0) { ERR(file->handle, "missing module in module package"); goto cleanup; } free(offsets); return 0; cleanup: free(offsets); return -1; } int sepol_module_package_info(struct sepol_policy_file *spf, int *type, char **name, char **version) { struct policy_file *file = &spf->pf; sepol_module_package_t *mod = NULL; uint32_t buf[5], len, nsec; size_t *offsets = NULL; unsigned i, seen = 0; char *id; int rc; if (sepol_module_package_create(&mod)) return -1; if (module_package_read_offsets(mod, file, &offsets, &nsec)) { goto cleanup; } for (i = 0; i < nsec; i++) { if (policy_file_seek(file, offsets[i])) { ERR(file->handle, "error seeking to offset " "%zu for module package section %u", offsets[i], i); goto cleanup; } len = offsets[i + 1] - offsets[i]; if (len < sizeof(uint32_t)) { ERR(file->handle, "module package section %u has too small length %u", i, len); goto cleanup; } /* read the magic number, so that we know which function to call */ rc = next_entry(buf, file, sizeof(uint32_t) * 2); if (rc < 0) { ERR(file->handle, "module package section %u truncated, lacks magic number", i); goto cleanup; } switch (le32_to_cpu(buf[0])) { case SEPOL_PACKAGE_SECTION_FC: /* skip file contexts */ if (seen & SEEN_FC) { ERR(file->handle, "found multiple file contexts sections in module package (at section %u)", i); goto cleanup; } seen |= SEEN_FC; break; case SEPOL_PACKAGE_SECTION_SEUSER: /* skip seuser */ if (seen & SEEN_SEUSER) { ERR(file->handle, "found seuser sections in module package (at section %u)", i); goto cleanup; } seen |= SEEN_SEUSER; break; case SEPOL_PACKAGE_SECTION_USER_EXTRA: /* skip user_extra */ if (seen & SEEN_USER_EXTRA) { ERR(file->handle, "found user_extra sections in module package (at section %u)", i); goto cleanup; } seen |= SEEN_USER_EXTRA; break; case SEPOL_PACKAGE_SECTION_NETFILTER: /* skip netfilter contexts */ if (seen & SEEN_NETFILTER) { ERR(file->handle, "found multiple netfilter contexts sections in module package (at section %u)", i); goto cleanup; } seen |= SEEN_NETFILTER; break; case POLICYDB_MOD_MAGIC: if (seen & SEEN_MOD) { ERR(file->handle, "found multiple module sections in module package (at section %u)", i); goto cleanup; } len = le32_to_cpu(buf[1]); if (len != strlen(POLICYDB_MOD_STRING)) { ERR(file->handle, "module string length is wrong (at section %u)", i); goto cleanup; } /* skip id */ id = malloc(len + 1); if (!id) { ERR(file->handle, "out of memory (at section %u)", i); goto cleanup; } rc = next_entry(id, file, len); free(id); if (rc < 0) { ERR(file->handle, "cannot get module string (at section %u)", i); goto cleanup; } rc = next_entry(buf, file, sizeof(uint32_t) * 5); if (rc < 0) { ERR(file->handle, "cannot get module header (at section %u)", i); goto cleanup; } *type = le32_to_cpu(buf[0]); /* if base - we're done */ if (*type == POLICY_BASE) { *name = NULL; *version = NULL; seen |= SEEN_MOD; break; } else if (*type != POLICY_MOD) { ERR(file->handle, "module has invalid type %d (at section %u)", *type, i); goto cleanup; } /* read the name and version */ rc = next_entry(buf, file, sizeof(uint32_t)); if (rc < 0) { ERR(file->handle, "cannot get module name len (at section %u)", i); goto cleanup; } len = le32_to_cpu(buf[0]); if (str_read(name, file, len)) { ERR(file->handle, "%s", strerror(errno)); goto cleanup; } rc = next_entry(buf, file, sizeof(uint32_t)); if (rc < 0) { ERR(file->handle, "cannot get module version len (at section %u)", i); goto cleanup; } len = le32_to_cpu(buf[0]); if (str_read(version, file, len)) { ERR(file->handle, "%s", strerror(errno)); goto cleanup; } seen |= SEEN_MOD; break; default: break; } } if ((seen & SEEN_MOD) == 0) { ERR(file->handle, "missing module in module package"); goto cleanup; } sepol_module_package_free(mod); free(offsets); return 0; cleanup: sepol_module_package_free(mod); free(offsets); return -1; } static int write_helper(char *data, size_t len, struct policy_file *file) { int idx = 0; size_t len2; while (len) { if (len > BUFSIZ) len2 = BUFSIZ; else len2 = len; if (put_entry(&data[idx], 1, len2, file) != len2) { return -1; } len -= len2; idx += len2; } return 0; } int sepol_module_package_write(sepol_module_package_t * p, struct sepol_policy_file *spf) { struct policy_file *file = &spf->pf; policy_file_t polfile; uint32_t buf[5], offsets[5], len, nsec = 0; int i; if (p->policy) { /* compute policy length */ policy_file_init(&polfile); polfile.type = PF_LEN; polfile.handle = file->handle; if (policydb_write(&p->policy->p, &polfile)) return -1; len = polfile.len; if (!polfile.len) return -1; nsec++; } else { /* We don't support writing a package without a module at this point */ return -1; } /* seusers and user_extra only supported in base at the moment */ if ((p->seusers || p->user_extra) && (p->policy->p.policy_type != SEPOL_POLICY_BASE)) { ERR(file->handle, "seuser and user_extra sections only supported in base"); return -1; } if (p->file_contexts) nsec++; if (p->seusers) nsec++; if (p->user_extra) nsec++; if (p->netfilter_contexts) nsec++; buf[0] = cpu_to_le32(SEPOL_MODULE_PACKAGE_MAGIC); buf[1] = cpu_to_le32(p->version); buf[2] = cpu_to_le32(nsec); if (put_entry(buf, sizeof(uint32_t), 3, file) != 3) return -1; /* calculate offsets */ offsets[0] = (nsec + 3) * sizeof(uint32_t); buf[0] = cpu_to_le32(offsets[0]); i = 1; if (p->file_contexts) { offsets[i] = offsets[i - 1] + len; buf[i] = cpu_to_le32(offsets[i]); /* add a uint32_t to compensate for the magic number */ len = p->file_contexts_len + sizeof(uint32_t); i++; } if (p->seusers) { offsets[i] = offsets[i - 1] + len; buf[i] = cpu_to_le32(offsets[i]); len = p->seusers_len + sizeof(uint32_t); i++; } if (p->user_extra) { offsets[i] = offsets[i - 1] + len; buf[i] = cpu_to_le32(offsets[i]); len = p->user_extra_len + sizeof(uint32_t); i++; } if (p->netfilter_contexts) { offsets[i] = offsets[i - 1] + len; buf[i] = cpu_to_le32(offsets[i]); len = p->netfilter_contexts_len + sizeof(uint32_t); i++; } if (put_entry(buf, sizeof(uint32_t), nsec, file) != nsec) return -1; /* write sections */ if (policydb_write(&p->policy->p, file)) return -1; if (p->file_contexts) { buf[0] = cpu_to_le32(SEPOL_PACKAGE_SECTION_FC); if (put_entry(buf, sizeof(uint32_t), 1, file) != 1) return -1; if (write_helper(p->file_contexts, p->file_contexts_len, file)) return -1; } if (p->seusers) { buf[0] = cpu_to_le32(SEPOL_PACKAGE_SECTION_SEUSER); if (put_entry(buf, sizeof(uint32_t), 1, file) != 1) return -1; if (write_helper(p->seusers, p->seusers_len, file)) return -1; } if (p->user_extra) { buf[0] = cpu_to_le32(SEPOL_PACKAGE_SECTION_USER_EXTRA); if (put_entry(buf, sizeof(uint32_t), 1, file) != 1) return -1; if (write_helper(p->user_extra, p->user_extra_len, file)) return -1; } if (p->netfilter_contexts) { buf[0] = cpu_to_le32(SEPOL_PACKAGE_SECTION_NETFILTER); if (put_entry(buf, sizeof(uint32_t), 1, file) != 1) return -1; if (write_helper (p->netfilter_contexts, p->netfilter_contexts_len, file)) return -1; } return 0; } int sepol_link_modules(sepol_handle_t * handle, sepol_policydb_t * base, sepol_policydb_t ** modules, size_t len, int verbose) { return link_modules(handle, &base->p, (policydb_t **) modules, len, verbose); } int sepol_expand_module(sepol_handle_t * handle, sepol_policydb_t * base, sepol_policydb_t * out, int verbose, int check) { return expand_module(handle, &base->p, &out->p, verbose, check); }