summaryrefslogtreecommitdiffstats
path: root/libsefs/src/filesystem.cc
diff options
context:
space:
mode:
Diffstat (limited to 'libsefs/src/filesystem.cc')
-rw-r--r--libsefs/src/filesystem.cc733
1 files changed, 733 insertions, 0 deletions
diff --git a/libsefs/src/filesystem.cc b/libsefs/src/filesystem.cc
new file mode 100644
index 0000000..8dfd32b
--- /dev/null
+++ b/libsefs/src/filesystem.cc
@@ -0,0 +1,733 @@
+/**
+ * @file
+ * Implementation of the sefs_filesystem class.
+ *
+ * @author Jeremy A. Mowery jmowery@tresys.com
+ * @author Jason Tang jtang@tresys.com
+ *
+ * Copyright (C) 2007 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 <config.h>
+
+#include "sefs_internal.hh"
+#include "new_ftw.h"
+
+#include <sefs/entry.hh>
+#include <sefs/filesystem.hh>
+#include <apol/util.h>
+#include <selinux/context.h>
+#include <selinux/selinux.h>
+#include <assert.h>
+#include <errno.h>
+#include <mntent.h>
+#include <regex.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+extern int lgetfilecon_raw(const char *, security_context_t *) __attribute__ ((weak));
+
+/**
+ * As that setools must work with older libselinux versions that may
+ * not have the _raw() functions, declare them as weak. If libselinux
+ * does indeed have the new functions then use them; otherwise
+ * fallback to the originals.
+ */
+static int filesystem_lgetfilecon(const char *path, security_context_t * context)
+{
+ if (lgetfilecon_raw != NULL)
+ {
+ return lgetfilecon_raw(path, context);
+ }
+ else
+ {
+ return lgetfilecon(path, context);
+ }
+}
+
+#if 0
+
+/**
+ * Given a directory, find all bounded mounted filesystems within that
+ * directory (or subdirectory within.) This function consults the
+ * entries written to /etc/mtab to determine if something is mounted
+ * or not and if it has the "bind" option. Note that if \a dir itself
+ * is a mount, it will not be reported; a subdirectory might.
+ *
+ * Note that the returned vector in never actually used by this
+ * library. This function existed in previous versions of libsefs,
+ * but was never documented why it existed. Rather than eliminate
+ * this function, it is retained (but effectively unused), in case a
+ * future revision of libsefs necessitates finding bind mounts.
+ *
+ * @param dir Directory to begin search.
+ *
+ * @return An allocated vector containing pathnames (type char *) to
+ * each mounted location with the "bind" option. The caller is
+ * responsible for calling apol_vector_destroy() afterwards.
+ */
+static apol_vector_t *filesystem_find_mount_points(const char *dir) throw(std::bad_alloc, std::runtime_error)
+{
+ char *dirdup = NULL;
+ apol_vector_t *v = NULL;
+ FILE *mtab = NULL;
+ struct mntent *entry;
+
+ try
+ {
+ if ((dirdup = strdup(dir)) == NULL)
+ {
+ throw std::bad_alloc();
+ }
+ size_t len = strlen(dirdup);
+ if (len > 1 && dirdup[len - 1] == '/')
+ {
+ dirdup[len - 1] = '\0';
+ }
+ if ((v = apol_vector_create(free)) == NULL)
+ {
+ throw std::bad_alloc();
+ }
+ if ((mtab = fopen("/etc/mtab", "r")) == NULL)
+ {
+ throw std::runtime_error(strerror(errno));
+ }
+ // note non thread-safeness below
+ while ((entry = getmntent(mtab)) != NULL)
+ {
+ if (strstr(entry->mnt_dir, dir) != entry->mnt_dir)
+ {
+ continue;
+ }
+ if (strcmp(entry->mnt_dir, dirdup) == 0)
+ {
+ continue;
+ }
+ if (strstr(entry->mnt_opts, "bind") != NULL)
+ {
+ char *s = strdup(entry->mnt_dir);
+ if (s == NULL)
+ {
+ throw std::bad_alloc();
+ }
+ if (apol_vector_append(v, s) < 0)
+ {
+ free(s);
+ throw std::bad_alloc();
+ }
+ }
+ }
+
+ }
+ catch(...)
+ {
+ free(dirdup);
+ apol_vector_destroy(&v);
+ if (mtab != NULL)
+ {
+ fclose(mtab);
+ }
+ throw;
+ }
+ free(dirdup);
+ fclose(mtab);
+ return v;
+}
+
+#endif
+
+/******************** public functions below ********************/
+
+sefs_filesystem::sefs_filesystem(const char *new_root, sefs_callback_fn_t msg_callback, void *varg)throw(std::bad_alloc, std::invalid_argument, std::runtime_error):sefs_fclist(SEFS_FCLIST_TYPE_FILESYSTEM,
+ msg_callback,
+ varg)
+{
+ if (new_root == NULL)
+ {
+ SEFS_ERR(this, "%s", strerror(EINVAL));
+ errno = EINVAL;
+ throw std::invalid_argument(strerror(EINVAL));
+ }
+ _root = NULL;
+ _mls = false;
+ try
+ {
+ // check that root exists and is readable
+ struct stat64 sb;
+ if (stat64(new_root, &sb) != 0 && !S_ISDIR(sb.st_mode))
+ {
+ SEFS_ERR(this, "%s", strerror(EINVAL));
+ errno = EINVAL;
+ throw std::invalid_argument(strerror(EINVAL));
+ }
+
+ // determine if filesystem is MLS or not
+ security_context_t scon;
+ if (filesystem_lgetfilecon(new_root, &scon) < 0)
+ {
+ SEFS_ERR(this, "Could not read SELinux file context for %s.", new_root);
+ throw std::runtime_error(strerror(errno));
+ }
+ context_t con;
+ if ((con = context_new(scon)) == 0)
+ {
+ SEFS_ERR(this, "%s", strerror(errno));
+ freecon(scon);
+ throw std::runtime_error(strerror(errno));
+ }
+ freecon(scon);
+ const char *range = context_range_get(con);
+ if (range != NULL && range[0] != '\0')
+ {
+ _mls = true;
+ }
+ context_free(con);
+
+ if ((_root = strdup(new_root)) == NULL)
+ {
+ SEFS_ERR(this, "%s", strerror(errno));
+ throw std::bad_alloc();
+ }
+ }
+ catch(...)
+ {
+ free(_root);
+ throw;
+ }
+}
+
+sefs_filesystem::~sefs_filesystem()
+{
+ free(_root);
+}
+
+struct filesystem_ftw_struct
+{
+ sefs_filesystem *fs;
+ sefs_query *query;
+ apol_vector_t *dev_map; //< vector of filesystem_dev entries
+ apol_vector_t *type_list;
+ apol_mls_range_t *range;
+ sefs_fclist_map_fn_t fn;
+ void *data;
+ bool aborted;
+ int retval;
+};
+
+// wrapper functions to go between non-OO land into OO member functions
+
+inline struct sefs_context_node *filesystem_get_context(sefs_filesystem * fs, security_context_t scon) throw(std::bad_alloc)
+{
+ return fs->getContext(scon);
+}
+
+inline sefs_entry *filesystem_get_entry(sefs_filesystem * fs, const struct sefs_context_node * node, uint32_t objClass,
+ const char *path, ino64_t ino, const char *dev_name)throw(std::bad_alloc)
+{
+ return fs->getEntry(node, objClass, path, ino, dev_name);
+}
+
+inline bool filesystem_is_query_match(sefs_filesystem * fs, const sefs_query * query, const char *path, const char *dev,
+ const struct stat64 * sb, apol_vector_t * type_list,
+ apol_mls_range_t * range)throw(std::runtime_error)
+{
+ return fs->isQueryMatch(query, path, dev, sb, type_list, range);
+}
+
+static uint32_t filesystem_stat_to_objclass(const struct stat64 *sb)
+{
+ if (S_ISREG(sb->st_mode))
+ {
+ return QPOL_CLASS_FILE;
+ }
+ if (S_ISDIR(sb->st_mode))
+ {
+ return QPOL_CLASS_DIR;
+ }
+ if (S_ISCHR(sb->st_mode))
+ {
+ return QPOL_CLASS_CHR_FILE;
+ }
+ if (S_ISBLK(sb->st_mode))
+ {
+ return QPOL_CLASS_BLK_FILE;
+ }
+ if (S_ISFIFO(sb->st_mode))
+ {
+ return QPOL_CLASS_FIFO_FILE;
+ }
+ if (S_ISLNK(sb->st_mode))
+ {
+ return QPOL_CLASS_LNK_FILE;
+ }
+ if (S_ISSOCK(sb->st_mode))
+ {
+ return QPOL_CLASS_SOCK_FILE;
+ }
+ assert(0); // should never get here
+ return 0;
+}
+
+struct filesystem_dev
+{
+ dev_t dev;
+ char *dev_name; //< pointer into the dev_tree
+};
+
+static int filesystem_dev_cmp(const void *a, const void *b __attribute__ ((unused)), void *arg)
+{
+ const struct filesystem_dev *d1 = static_cast < const struct filesystem_dev *>(a);
+ dev_t *d2 = static_cast < dev_t * >(arg);
+ if (d1->dev < *d2)
+ {
+ return -1;
+ }
+ else if (d1->dev > *d2)
+ {
+ return 1;
+ }
+ return 0;
+}
+
+static int filesystem_ftw_handler(const char *fpath, const struct stat64 *sb, int typeflag
+ __attribute__ ((unused)), struct FTW *ftwbuf __attribute__ ((unused)), void *data)
+{
+ struct filesystem_ftw_struct *s = static_cast < struct filesystem_ftw_struct *>(data);
+
+ size_t i;
+ void *dev_num = const_cast < void *>(static_cast < const void *>(&(sb->st_dev)));
+ int rc = apol_vector_get_index(s->dev_map, NULL, filesystem_dev_cmp, dev_num, &i);
+ const char *dev = "<unknown>";
+ if (rc == 0)
+ {
+ // if the device number was discovered in buildDevMap
+ // then store the device name within the entry
+ struct filesystem_dev *d = static_cast < struct filesystem_dev *>(apol_vector_get_element(s->dev_map, i));
+ dev = d->dev_name;
+ }
+ else
+ {
+ SEFS_WARN(s->fs, "Unknown device for %s.", fpath);
+ }
+ try
+ {
+ if (!filesystem_is_query_match(s->fs, s->query, fpath, dev, sb, s->type_list, s->range))
+ {
+ return 0;
+ }
+ }
+ catch(...)
+ {
+ return -1;
+ }
+
+ security_context_t scon;
+ if (filesystem_lgetfilecon(fpath, &scon) < 0)
+ {
+ SEFS_ERR(s->fs, "Could not read SELinux file context for %s.", fpath);
+ return -1;
+ }
+ struct sefs_context_node *node = NULL;
+ try
+ {
+ node = filesystem_get_context(s->fs, scon);
+ }
+ catch(...)
+ {
+ freecon(scon);
+ return -1;
+ }
+ freecon(scon);
+
+ uint32_t objClass = filesystem_stat_to_objclass(sb);
+
+ sefs_entry *entry = NULL;
+ try
+ {
+ entry = filesystem_get_entry(s->fs, node, objClass, fpath, sb->st_ino, dev);
+ }
+ catch(...)
+ {
+ return -1;
+ }
+
+ // invoke real callback (not just the nftw handler)
+ s->retval = s->fn(s->fs, entry, s->data);
+ delete entry;
+ if (s->retval < 0)
+ {
+ s->aborted = true;
+ return s->retval;
+ }
+
+ return 0;
+}
+
+int sefs_filesystem::runQueryMap(sefs_query * query, sefs_fclist_map_fn_t fn, void *data) throw(std::runtime_error,
+ std::invalid_argument)
+{
+ struct filesystem_ftw_struct s;
+ s.dev_map = NULL;
+ s.type_list = NULL;
+ s.range = NULL;
+ try
+ {
+ s.dev_map = buildDevMap();
+ if (query != NULL)
+ {
+ query->compile();
+ if (policy != NULL)
+ {
+ if (query->_type != NULL && query->_indirect &&
+ (s.type_list =
+ query_create_candidate_type(policy, query->_type, query->_retype, query->_regex,
+ query->_indirect)) == NULL)
+ {
+ SEFS_ERR(this, "%s", strerror(errno));
+ throw std::runtime_error(strerror(errno));
+ }
+ if (query->_range != NULL && query->_rangeMatch != 0 &&
+ (s.range = apol_mls_range_create_from_string(policy, query->_range)) == NULL)
+ {
+ SEFS_ERR(this, "%s", strerror(errno));
+ throw std::runtime_error(strerror(errno));
+ }
+ }
+ }
+ }
+ catch(...)
+ {
+ apol_vector_destroy(&s.dev_map);
+ apol_vector_destroy(&s.type_list);
+ apol_mls_range_destroy(&s.range);
+ throw;
+ }
+ s.fs = this;
+ s.query = query;
+ s.fn = fn;
+ s.data = data;
+ s.aborted = false;
+ s.retval = 0;
+
+ int retval = new_nftw64(_root, filesystem_ftw_handler, 1024, 0, &s);
+ apol_vector_destroy(&s.dev_map);
+ apol_vector_destroy(&s.type_list);
+ apol_mls_range_destroy(&s.range);
+ if (retval != 0 && !s.aborted)
+ {
+ // error was generated by new_nftw64() itself, not
+ // from callback
+ return retval;
+ }
+ return s.retval;
+}
+
+bool sefs_filesystem::isMLS() const
+{
+ return _mls;
+}
+
+const char *sefs_filesystem::root() const
+{
+ return _root;
+}
+
+/******************** private functions below ********************/
+
+static void filesystem_dev_free(void *elem)
+{
+ if (elem != NULL)
+ {
+ struct filesystem_dev *d = static_cast < struct filesystem_dev *>(elem);
+ // don't free the device name pointer, because it's pointing
+ // into the dev_tree BST
+ free(d);
+ }
+}
+
+const char *sefs_filesystem::getDevName(const dev_t dev) throw(std::runtime_error)
+{
+ apol_vector_t *dev_map = buildDevMap();
+ size_t i;
+ void *devp = const_cast < dev_t * >(&dev);
+ int rc = apol_vector_get_index(dev_map, NULL, filesystem_dev_cmp, devp, &i);
+ if (rc < 0)
+ {
+ apol_vector_destroy(&dev_map);
+ return NULL;
+ }
+ struct filesystem_dev *d = static_cast < struct filesystem_dev *>(apol_vector_get_element(dev_map, i));
+ const char *dev_name = d->dev_name; // this is pointing into this->_dev_tree
+ apol_vector_destroy(&dev_map);
+ return dev_name;
+}
+
+/**
+ * For each entry in /etc/mtab, record the device number and the name
+ * of the mounted file system. This provides the mapping between a
+ * device number and its source device.
+ *
+ * @return Vector of filesystem_dev entries. The caller must call
+ * apol_vector_destroy() upon the vector afterwards.
+ * @exception If error allocating space, unable to open /etc/mtab, or
+ * unable to parse mtab file.
+ */
+apol_vector_t *sefs_filesystem::buildDevMap(void)throw(std::runtime_error)
+{
+ apol_vector_t *dev_map;
+ if ((dev_map = apol_vector_create(filesystem_dev_free)) == NULL)
+ {
+ SEFS_ERR(this, "%s", strerror(errno));
+ throw std::runtime_error(strerror(errno));
+ }
+ FILE *f = NULL;
+ try
+ {
+ if ((f = fopen("/etc/mtab", "r")) == NULL)
+ {
+ SEFS_ERR(this, "%s", strerror(errno));
+ throw std::runtime_error(strerror(errno));
+ }
+ char buf[256];
+ struct mntent mntbuf;
+ while (getmntent_r(f, &mntbuf, buf, 256) != NULL)
+ {
+ struct stat sb;
+ if (stat(mntbuf.mnt_dir, &sb) == -1)
+ {
+ // could not open this device, so skip
+ // it (and hope it won't be examined
+ // during runQuery())
+ continue;
+ }
+ else
+ {
+ struct filesystem_dev *d = static_cast < struct filesystem_dev *>(calloc(1, sizeof(*d)));
+ if (d == NULL)
+ {
+ SEFS_ERR(this, "%s", strerror(errno));
+ throw std::runtime_error(strerror(errno));
+ }
+ if (apol_vector_append(dev_map, d) < 0)
+ {
+ SEFS_ERR(this, "%s", strerror(errno));
+ filesystem_dev_free(d);
+ throw std::runtime_error(strerror(errno));
+ }
+ d->dev = sb.st_dev;
+ char *mnt_fsname = strdup(mntbuf.mnt_fsname);
+ if (mnt_fsname == NULL)
+ {
+ SEFS_ERR(this, "%s", strerror(errno));
+ throw std::runtime_error(strerror(errno));
+ }
+ if (apol_bst_insert_and_get(dev_tree, (void **)&mnt_fsname, NULL) < 0)
+ {
+ SEFS_ERR(this, "%s", strerror(errno));
+ free(mnt_fsname);
+ throw std::runtime_error(strerror(errno));
+ }
+ d->dev_name = mnt_fsname;
+ }
+ }
+ }
+ catch(...)
+ {
+ apol_vector_destroy(&dev_map);
+ if (f != NULL)
+ {
+ fclose(f);
+ }
+ throw;
+ }
+ fclose(f);
+ return dev_map;
+}
+
+bool sefs_filesystem::isQueryMatch(const sefs_query * query, const char *path, const char *dev, const struct stat64 * sb,
+ apol_vector_t * type_list, apol_mls_range_t * range)throw(std::runtime_error)
+{
+ if (query == NULL)
+ {
+ return true;
+ }
+ security_context_t scon;
+ if (filesystem_lgetfilecon(path, &scon) < 0)
+ {
+ SEFS_ERR(this, "%s", strerror(errno));
+ throw std::runtime_error(strerror(errno));
+ }
+ context_t con;
+ if ((con = context_new(scon)) == 0)
+ {
+ SEFS_ERR(this, "%s", strerror(errno));
+ freecon(scon);
+ throw std::runtime_error(strerror(errno));
+ }
+ freecon(scon);
+
+ if (!query_str_compare(context_user_get(con), query->_user, query->_reuser, query->_regex))
+ {
+ context_free(con);
+ return false;
+ }
+ if (!query_str_compare(context_role_get(con), query->_role, query->_rerole, query->_regex))
+ {
+ context_free(con);
+ return false;
+ }
+
+ bool str_matched = false, pol_matched = false;
+ str_matched = query_str_compare(context_type_get(con), query->_type, query->_retype, query->_regex);
+ if (type_list != NULL && !str_matched)
+ {
+ size_t index;
+ pol_matched = (apol_vector_get_index(type_list, context_type_get(con), apol_str_strcmp, NULL, &index) < 0);
+ }
+ if (!str_matched && !pol_matched)
+ {
+ context_free(con);
+ return false;
+ }
+
+ if (isMLS())
+ {
+ if (range == NULL)
+ {
+ if (!query_str_compare(context_range_get(con), query->_range, query->_rerange, query->_regex))
+ {
+ context_free(con);
+ return false;
+ }
+ }
+ else
+ {
+ assert(policy != NULL);
+ apol_mls_range_t *context_range = apol_mls_range_create_from_string(policy, context_range_get(con));
+ if (context_range == NULL)
+ {
+ SEFS_ERR(this, "%s", strerror(errno));
+ context_free(con);
+ throw std::runtime_error(strerror(errno));
+ }
+ int ret;
+ ret = apol_mls_range_compare(policy, range, context_range, query->_rangeMatch);
+ apol_mls_range_destroy(&context_range);
+ if (ret <= 0)
+ {
+ context_free(con);
+ return false;
+ }
+ }
+ }
+
+ context_free(con);
+
+ if (query->_objclass != 0 && query->_objclass != filesystem_stat_to_objclass(sb))
+ {
+ return false;
+ }
+
+ if (!query_str_compare(path, query->_path, query->_repath, query->_regex))
+ {
+ return false;
+ }
+
+ if (query->_inode != 0 && query->_inode != sb->st_ino)
+ {
+ return false;
+ }
+
+ if (!query_str_compare(dev, query->_dev, query->_redev, query->_regex))
+ {
+ return false;
+ }
+
+ return true;
+}
+
+sefs_entry *sefs_filesystem::getEntry(const struct sefs_context_node * context, uint32_t objectClass,
+ const char *path, ino64_t ino, const char *dev_name)throw(std::bad_alloc)
+{
+ char *s = strdup(path);
+ if (s == NULL)
+ {
+ SEFS_ERR(this, "%s", strerror(errno));
+ throw std::bad_alloc();
+ }
+ if (apol_bst_insert_and_get(path_tree, (void **)&s, NULL) < 0)
+ {
+ SEFS_ERR(this, "%s", strerror(errno));
+ free(s);
+ throw std::bad_alloc();
+ }
+ sefs_entry *e = new sefs_entry(this, context, objectClass, s);
+ e->_inode = ino;
+ e->_dev = dev_name;
+ return e;
+}
+
+/******************** C functions below ********************/
+
+sefs_filesystem_t *sefs_filesystem_create(const char *root, sefs_callback_fn_t msg_callback, void *varg)
+{
+ sefs_filesystem_t *fs;
+ try
+ {
+ fs = new sefs_filesystem(root, msg_callback, varg);
+ }
+ catch(...)
+ {
+ errno = ENOMEM;
+ return NULL;
+ }
+ return fs;
+}
+
+const char *sefs_filesystem_get_root(const sefs_filesystem_t * fs)
+{
+ if (fs == NULL)
+ {
+ SEFS_ERR(NULL, "%s", strerror(EINVAL));
+ errno = EINVAL;
+ return NULL;
+ }
+ return fs->root();
+}
+
+extern const char *sefs_filesystem_get_dev_name(sefs_filesystem_t * fs, const dev_t dev)
+{
+ if (fs == NULL)
+ {
+ SEFS_ERR(NULL, "%s", strerror(EINVAL));
+ errno = EINVAL;
+ return NULL;
+ }
+ const char *dev_name = NULL;
+ try
+ {
+ dev_name = fs->getDevName(dev);
+ }
+ catch(...)
+ {
+ return NULL;
+ }
+ return dev_name;
+}