summaryrefslogtreecommitdiffstats
path: root/libapol/src/infoflow-analysis.c
diff options
context:
space:
mode:
authorMiroslav Grepl <mgrepl@redhat.com>2014-04-11 09:37:53 +0200
committerMiroslav Grepl <mgrepl@redhat.com>2014-04-11 09:37:53 +0200
commit47be9ff57e72906660bb62a515222f482131e1fb (patch)
tree2cb0ef0ba48d73b1df7cc0915754a17e19464bb6 /libapol/src/infoflow-analysis.c
downloadsetools-master.tar.gz
setools-master.tar.xz
setools-master.zip
Create setools-3.3.7 git repomaster
Diffstat (limited to 'libapol/src/infoflow-analysis.c')
-rw-r--r--libapol/src/infoflow-analysis.c2245
1 files changed, 2245 insertions, 0 deletions
diff --git a/libapol/src/infoflow-analysis.c b/libapol/src/infoflow-analysis.c
new file mode 100644
index 0000000..dad9a34
--- /dev/null
+++ b/libapol/src/infoflow-analysis.c
@@ -0,0 +1,2245 @@
+/**
+ * @file
+ * Implementation of the information flow analysis.
+ *
+ * @author Jeremy A. Mowery jmowery@tresys.com
+ * @author Jason Tang jtang@tresys.com
+ *
+ * Copyright (C) 2003-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 "policy-query-internal.h"
+#include "infoflow-analysis-internal.h"
+#include "queue.h"
+#include <apol/bst.h>
+#include <apol/perm-map.h>
+
+#include <assert.h>
+#include <config.h>
+#include <errno.h>
+#include <limits.h>
+#include <time.h>
+
+/*
+ * Nodes in the graph represent either a type used in the source
+ * of an allow rule or the target: these defines are used to
+ * represent which.
+ */
+#define APOL_INFOFLOW_NODE_SOURCE 0x1
+#define APOL_INFOFLOW_NODE_TARGET 0x2
+
+/*
+ * These defines are used to color nodes in the graph algorithms.
+ */
+#define APOL_INFOFLOW_COLOR_WHITE 0
+#define APOL_INFOFLOW_COLOR_GREY 1
+#define APOL_INFOFLOW_COLOR_BLACK 2
+#define APOL_INFOFLOW_COLOR_RED 3
+
+typedef struct apol_infoflow_node apol_infoflow_node_t;
+typedef struct apol_infoflow_edge apol_infoflow_edge_t;
+
+struct apol_infoflow_graph
+{
+ /** vector of apol_infoflow_node_t */
+ apol_vector_t *nodes;
+ /** vector of apol_infoflow_edge_t */
+ apol_vector_t *edges;
+ /** temporary BST of apol_infoflow_node_t used while building
+ * the graph */
+ apol_bst_t *nodes_bst;
+
+ unsigned int mode, direction;
+ regex_t *regex;
+
+ /** vector of apol_infoflow_node_t, used for random restarts
+ * for further transitive analysis */
+ apol_vector_t *further_start;
+ /** vector of apol_infoflow_node_t of targets, used for
+ * further transitive analysis */
+ apol_vector_t *further_end;
+ size_t current_start;
+#ifdef HAVE_RAND_R
+ unsigned int seed;
+#endif
+};
+
+struct apol_infoflow_node
+{
+ const qpol_type_t *type;
+ /** one of APOL_INFOFLOW_NODE_SOURCE or APOL_INFOFLOW_NODE_TARGET */
+ int node_type;
+ /** vector of apol_infoflow_edge_t, pointing into the graph */
+ apol_vector_t *in_edges;
+ /** vector of apol_infoflow_edge_t, pointing into the graph */
+ apol_vector_t *out_edges;
+ unsigned char color;
+ apol_infoflow_node_t *parent;
+ int distance;
+};
+
+struct apol_infoflow_edge
+{
+ /** vector of qpol_avrule_t, pointing into the policy */
+ apol_vector_t *rules;
+ /** pointer into a node within the graph */
+ apol_infoflow_node_t *start_node;
+ /** pointer into a node within the graph */
+ apol_infoflow_node_t *end_node;
+ int length;
+};
+
+/**
+ * apol_infoflow_analysis_h encapsulates all of the paramaters of a
+ * query. It should always be allocated with
+ * apol_infoflow_analysis_create() and deallocated with
+ * apol_infoflow_analysis_destroy(). Limiting by ending_types,
+ * obj_classes, intermed types, obj_class permissions is optional - if
+ * the vector is empty then no limiting is done.
+ *
+ * All of the vectors except end_types should contain the items that
+ * you want to not appear in the results. end_types lists the types
+ * that you do want to appear.
+ */
+struct apol_infoflow_analysis
+{
+ unsigned int mode, direction;
+ char *type, *result;
+ apol_vector_t *intermed, *class_perms;
+ int min_weight;
+};
+
+/**
+ * The results of running an infoflow, either direct or transitive, is
+ * a path from start_type to end_type. The path consists of a vector
+ * of intermediate steps.
+ */
+struct apol_infoflow_result
+{
+ const qpol_type_t *start_type, *end_type;
+ /** vector of apol_infoflow_step_t */
+ apol_vector_t *steps;
+ unsigned int direction;
+ unsigned int length;
+};
+
+/**
+ * Each result consists of multiple steps, representing the steps
+ * taken from the original start to end types. Along each step there
+ * is a vector of rules. For a direct infoflow analysis there will be
+ * exactly one step, and that flow's start type is the same as the
+ * original result's start_type. Likewise the end_types will be the
+ * same.
+ */
+struct apol_infoflow_step
+{
+ const qpol_type_t *start_type, *end_type;
+ /** vector of qpol_avrule_t */
+ apol_vector_t *rules;
+ int weight;
+};
+
+/**
+ * Deallocate all space associated with an apol_infoflow_step_t,
+ * including the pointer itself. Does nothing if the pointer is
+ * already NULL.
+ *
+ * @param step Infoflow step to free.
+ */
+static void apol_infoflow_step_free(void *step)
+{
+ if (step != NULL) {
+ apol_infoflow_step_t *s = (apol_infoflow_step_t *) step;
+ apol_vector_destroy(&s->rules);
+ free(s);
+ }
+}
+
+/******************** random number routines ********************/
+
+/**
+ * Initialize the pseudo-random number generator to be used during
+ * further transitive analysis.
+ *
+ * @param g Transitive infoflow graph.
+ */
+static void apol_infoflow_srand(apol_infoflow_graph_t * g)
+{
+#ifdef HAVE_RAND_R
+ g->seed = (int)time(NULL);
+#else
+ srand((int)time(NULL));
+#endif
+}
+
+/**
+ * Return a pseudo-random integer between 0 and RAND_MAX, for use
+ * during further transitive analysis. If the system supports it,
+ * this function will use rand_r() so that this library remains
+ * reentrant and thread-safe.
+ *
+ * @param g Transitive infoflow graph.
+ *
+ * @return Integer between 0 and RAND_MAX.
+ */
+static int apol_infoflow_rand(apol_infoflow_graph_t * g)
+{
+#ifdef HAVE_RAND_R
+ return rand_r(&g->seed);
+#else
+ return rand();
+#endif
+}
+
+/******************** infoflow graph node routines ********************/
+
+/**
+ * Given a pointer to an apol_infoflow_node_t, free its space
+ * including the pointer itself. Does nothing if the pointer is
+ * already NULL.
+ *
+ * @param data Node to free.
+ */
+static void apol_infoflow_node_free(void *data)
+{
+ apol_infoflow_node_t *node = (apol_infoflow_node_t *) data;
+ if (node != NULL) {
+ /* the edges themselves are owned by the graph, not by
+ * the node */
+ apol_vector_destroy(&node->in_edges);
+ apol_vector_destroy(&node->out_edges);
+ free(node);
+ }
+}
+
+struct apol_infoflow_node_key
+{
+ const qpol_type_t *type;
+ int node_type;
+};
+
+/**
+ * Given an infoflow node and a key, returns 0 if they are the same,
+ * non-zero if not.
+ *
+ * @param a Existing node within the infoflow graph.
+ * @param b <i>Unused.</i>
+ * @param data Pointer to a struct infoflow_node_key.
+ *
+ * @return 0 if the key matches a, non-zero if not.
+ */
+static int apol_infoflow_node_compare(const void *a, const void *b __attribute__ ((unused)), void *data)
+{
+ apol_infoflow_node_t *node = (apol_infoflow_node_t *) a;
+ struct apol_infoflow_node_key *key = (struct apol_infoflow_node_key *)data;
+ if (node->type != key->type) {
+ return (int)((char *)node->type - (char *)key->type);
+ }
+ return node->node_type - key->node_type;
+}
+
+/**
+ * Attempt to allocate a new node, add it to the infoflow graph, and
+ * return a pointer to it. If there already exists a node with the
+ * same type then reuse that node.
+ *
+ * @param p Policy handler, for reporting error.
+ * @param g Infoflow to which add the node.
+ * @param type Type for the new node.
+ * @param node_type Node type, one of APOL_INFOFLOW_NODE_SOURCE or
+ * APOL_INFOFLOW_NODE_TARGET.
+ *
+ * @return Pointer an allocated node within the infoflow graph, or
+ * NULL upon error.
+ */
+static apol_infoflow_node_t *apol_infoflow_graph_create_node(const apol_policy_t * p,
+ apol_infoflow_graph_t * g, const qpol_type_t * type, int node_type)
+{
+ struct apol_infoflow_node_key key = { type, node_type };
+ apol_infoflow_node_t *node = NULL;
+ if (apol_bst_get_element(g->nodes_bst, NULL, &key, (void **)&node) == 0) {
+ return node;
+ }
+ if ((node = calloc(1, sizeof(*node))) == NULL ||
+ (node->in_edges = apol_vector_create(NULL)) == NULL || (node->out_edges = apol_vector_create(NULL)) == NULL) {
+ ERR(p, "%s", strerror(errno));
+ apol_infoflow_node_free(node);
+ return NULL;
+ }
+ node->type = type;
+ node->node_type = node_type;
+ if (apol_bst_insert(g->nodes_bst, node, &key) != 0) {
+ ERR(p, "%s", strerror(errno));
+ apol_infoflow_node_free(node);
+ return NULL;
+ }
+ return node;
+}
+
+/**
+ * Attempt to allocate a new node, add it to the infoflow graph, and
+ * return a pointer to it. If there already exists a node with the
+ * same type then reuse that node.
+ *
+ * @param p Policy handler, for reporting error.
+ * @param g Infoflow to which add the node.
+ * @param type Type for the new node. If this is an attribute then it
+ * will be expanded into its component types.
+ * @param types If non-NULL, a BST of qpol_type_t pointers. Only
+ * create and return nodes which are members of this tree.
+ * @param node_type Node type, one of APOL_INFOFLOW_NODE_SOURCE or
+ * APOL_INFOFLOW_NODE_TARGET.
+ *
+ * @return Vector of nodes (type apol_infoflow_node_t *) within the
+ * infoflow graph, or NULL upon error. The caller is responsible for
+ * calling apol_vector_destroy() upon the return value.
+ */
+static apol_vector_t *apol_infoflow_graph_create_nodes(const apol_policy_t * p,
+ apol_infoflow_graph_t * g, const qpol_type_t * type, apol_bst_t * types,
+ int node_type)
+{
+ unsigned char isattr;
+ apol_vector_t *v = NULL;
+ apol_infoflow_node_t *node = NULL;
+ if (qpol_type_get_isattr(p->p, type, &isattr) < 0) {
+ return NULL;
+ }
+ if (isattr && g->mode != APOL_INFOFLOW_MODE_DIRECT) {
+ qpol_iterator_t *iter = NULL;
+ qpol_type_t *t;
+ size_t len;
+ if (qpol_type_get_type_iter(p->p, type, &iter) < 0 ||
+ qpol_iterator_get_size(iter, &len) < 0 || (v = apol_vector_create_with_capacity(len, NULL)) == NULL) {
+ qpol_iterator_destroy(&iter);
+ apol_vector_destroy(&v);
+ return NULL;
+ }
+ for (; !qpol_iterator_end(iter); qpol_iterator_next(iter)) {
+ qpol_iterator_get_item(iter, (void **)&t);
+ void *result;
+ if (types != NULL && apol_bst_get_element(types, t, NULL, &result) < 0) {
+ continue;
+ }
+ if ((node = apol_infoflow_graph_create_node(p, g, t, node_type)) == NULL || apol_vector_append(v, node) < 0) {
+ qpol_iterator_destroy(&iter);
+ apol_vector_destroy(&v);
+ return NULL;
+ }
+ }
+ qpol_iterator_destroy(&iter);
+ } else {
+ /* for a direct search, do not expand types; the
+ * algorithm will do that with
+ * apol_infoflow_graph_get_nodes_for_type() and
+ * apol_infoflow_analysis_direct_expand(). for
+ * transitive searches the \a types BST was checked in
+ * apol_infoflow_graph_check_types() if \a type is
+ * just a type.
+ */
+ if ((v = apol_vector_create_with_capacity(1, NULL)) == NULL) {
+ return NULL;
+ }
+ if ((node = apol_infoflow_graph_create_node(p, g, type, node_type)) == NULL || apol_vector_append(v, node) < 0) {
+ apol_vector_destroy(&v);
+ return NULL;
+ }
+ }
+ return v;
+}
+
+/******************** infoflow graph edge routines ********************/
+
+/**
+ * Given a pointer to an apol_infoflow_edge_t, free its space
+ * including the pointer itself. Does nothing if the pointer is
+ * already NULL.
+ *
+ * @param data Edge to free.
+ */
+static void apol_infoflow_edge_free(void *data)
+{
+ apol_infoflow_edge_t *edge = (apol_infoflow_edge_t *) data;
+ if (edge != NULL) {
+ apol_vector_destroy(&edge->rules);
+ free(edge);
+ }
+}
+
+struct apol_infoflow_edge_key
+{
+ apol_infoflow_node_t *start_node, *end_node;
+};
+
+/**
+ * Given an infoflow edge and a key, returns 0 if they are the same,
+ * non-zero if not.
+ *
+ * @param a Existing edge within the infoflow graph.
+ * @param b <i>Unused.</i>
+ * @param data Pointer to a struct infoflow_edge_key.
+ *
+ * @return 0 if the key matches a, non-zero if not.
+ */
+static int apol_infoflow_edge_compare(const void *a, const void *b __attribute__ ((unused)), void *data)
+{
+ apol_infoflow_edge_t *edge = (apol_infoflow_edge_t *) a;
+ struct apol_infoflow_edge_key *key = (struct apol_infoflow_edge_key *)data;
+ if (key->start_node != NULL && edge->start_node != key->start_node) {
+ return (int)((char *)edge->start_node - (char *)key->start_node);
+ }
+ if (key->end_node != NULL && edge->end_node != key->end_node) {
+ return (int)((char *)edge->end_node - (char *)key->end_node);
+ }
+ return 0;
+}
+
+/**
+ * Attempt to allocate a new edge, add it to the infoflow graph, and
+ * return a pointer to it. If there already exists a edge from the
+ * start node to the end node then reuse that edge.
+ *
+ * @param p Policy handler, for reporting errors.
+ * @param g Infoflow graph to which add the edge.
+ * @param start_node Starting node for the edge.
+ * @param end_node Ending node for the edge.
+ * @param len Length of edge (proportionally inverse of permission weight)
+ *
+ * @return Pointer an allocated node within the infoflow graph, or
+ * NULL upon error.
+ */
+static apol_infoflow_edge_t *apol_infoflow_graph_create_edge(const apol_policy_t * p,
+ apol_infoflow_graph_t * g __attribute__ ((unused)),
+ apol_infoflow_node_t * start_node,
+ apol_infoflow_node_t * end_node, int len)
+{
+ struct apol_infoflow_edge_key key = { NULL, end_node };
+ size_t i;
+ apol_infoflow_edge_t *edge = NULL;
+ if (apol_vector_get_index(start_node->out_edges, NULL, apol_infoflow_edge_compare, &key, &i) == 0) {
+ edge = (apol_infoflow_edge_t *) apol_vector_get_element(start_node->out_edges, i);
+ if (edge->length < len) {
+ edge->length = len;
+ }
+ return edge;
+ }
+ if ((edge = calloc(1, sizeof(*edge))) == NULL || (edge->rules = apol_vector_create(NULL)) == NULL ||
+ apol_vector_append(g->edges, edge) < 0) {
+ ERR(p, "%s", strerror(errno));
+ apol_infoflow_edge_free(edge);
+ return NULL;
+ }
+ edge->start_node = start_node;
+ edge->end_node = end_node;
+ edge->length = len;
+ if (apol_vector_append(start_node->out_edges, edge) < 0 || apol_vector_append(end_node->in_edges, edge) < 0) {
+ /* don't free the edge -- it is owned by the graph */
+ ERR(p, "%s", strerror(errno));
+ return NULL;
+ }
+ return edge;
+}
+
+/******************** infoflow graph creation routines ********************/
+
+/**
+ * Take an avrule within a policy and possibly add it to the infoflow
+ * graph. The rule's source and target type sets are expanded. If
+ * the rule is to be added, then add its end nodes as necessary, and
+ * an edge connecting those nodes as necessary, and then add the rule
+ * to the edge.
+ *
+ * @param p Policy containing rules.
+ * @param g Information flow graph being created.
+ * @param rule AV rule to use.
+ * @param types BST of qpol_type_t pointers; while adding avrules to
+ * the graph, only add those whose source and/or target is a member of
+ * \a types, if \a types is non-NULL.
+ * @param found_read Non-zero to indicate that this rule performs a
+ * read operation.
+ * @param read_len Length of the edge to create (proportionally
+ * inverse of permission weight).
+ * @param found_write Non-zero to indicate that this rule performs a
+ * write operation.
+ * @param write_len Length of the edge to create (proportionally
+ * inverse of permission weight).
+ *
+ * @return 0 on success, < 0 on error.
+ */
+static int apol_infoflow_graph_connect_nodes(const apol_policy_t * p,
+ apol_infoflow_graph_t * g,
+ const qpol_avrule_t * rule,
+ apol_bst_t * types, int found_read, int read_len, int found_write, int write_len)
+{
+ const qpol_type_t *src_type, *tgt_type;
+ apol_vector_t *src_nodes = NULL, *tgt_nodes = NULL;
+ size_t i, j;
+ apol_infoflow_node_t *src_node, *tgt_node;
+ apol_infoflow_edge_t *edge;
+ int retval = -1;
+
+ if (qpol_avrule_get_source_type(p->p, rule, &src_type) < 0 || qpol_avrule_get_target_type(p->p, rule, &tgt_type) < 0) {
+ goto cleanup;
+ }
+
+ if ((src_nodes = apol_infoflow_graph_create_nodes(p, g, src_type, types, APOL_INFOFLOW_NODE_SOURCE)) == NULL) {
+ goto cleanup;
+ }
+ if ((tgt_nodes = apol_infoflow_graph_create_nodes(p, g, tgt_type, types, APOL_INFOFLOW_NODE_TARGET)) == NULL) {
+ goto cleanup;
+ }
+ for (i = 0; i < apol_vector_get_size(src_nodes); i++) {
+ src_node = apol_vector_get_element(src_nodes, i);
+ for (j = 0; j < apol_vector_get_size(tgt_nodes); j++) {
+ tgt_node = apol_vector_get_element(tgt_nodes, j);
+ if (found_read) {
+ if ((edge = apol_infoflow_graph_create_edge(p, g, tgt_node, src_node, read_len)) == NULL) {
+ goto cleanup;
+ }
+ if (apol_vector_append(edge->rules, (void *)rule) < 0) {
+ ERR(p, "%s", strerror(ENOMEM));
+ goto cleanup;
+ }
+ }
+ if (found_write) {
+ if ((edge = apol_infoflow_graph_create_edge(p, g, src_node, tgt_node, write_len)) == NULL) {
+ goto cleanup;
+ }
+ if (apol_vector_append(edge->rules, (void *)rule) < 0) {
+ ERR(p, "%s", strerror(ENOMEM));
+ goto cleanup;
+ }
+ }
+ }
+ }
+ retval = 0;
+ cleanup:
+ apol_vector_destroy(&src_nodes);
+ apol_vector_destroy(&tgt_nodes);
+ return retval;
+}
+
+/**
+ * Given a policy and a partially completed infoflow graph, create the
+ * nodes and edges associated with a particular rule.
+ *
+ * @param p Policy from which to create the infoflow graph.
+ * @param g Infoflow graph being created.
+ * @param rule AV rule to add.
+ * @param types BST of qpol_type_t pointers; while adding avrules to
+ * the graph, only add those whose source and/or target is a member of
+ * \a types, if \a types is non-NULL.
+ * @param max_len Maximum permission length (i.e., inverse of
+ * permission weight) to consider when deciding to add this rule or
+ * not.
+ *
+ * @return 0 on success, < 0 on error.
+ */
+static int apol_infoflow_graph_create_avrule(const apol_policy_t * p, apol_infoflow_graph_t * g, const qpol_avrule_t * rule,
+ apol_bst_t * types, int max_len)
+{
+ const qpol_class_t *obj_class;
+ qpol_iterator_t *perm_iter = NULL;
+ const char *obj_class_name;
+ char *perm_name;
+ int found_read = 0, found_write = 0, perm_error = 0;
+ int read_len = INT_MAX, write_len = INT_MAX;
+ int retval = -1;
+ if (qpol_avrule_get_object_class(p->p, rule, &obj_class) < 0 ||
+ qpol_class_get_name(p->p, obj_class, &obj_class_name) < 0 || qpol_avrule_get_perm_iter(p->p, rule, &perm_iter) < 0) {
+ goto cleanup;
+ }
+
+ /* find read or write flows for each object class/perm pair */
+ for (; !qpol_iterator_end(perm_iter); qpol_iterator_next(perm_iter)) {
+ int perm_map, perm_weight, len;
+
+ if (qpol_iterator_get_item(perm_iter, (void **)&perm_name) < 0) {
+ goto cleanup;
+ }
+ if (apol_policy_get_permmap(p, obj_class_name, perm_name, &perm_map, &perm_weight) < 0) {
+ goto cleanup;
+ }
+ free(perm_name);
+ if (perm_map == APOL_PERMMAP_UNMAPPED) {
+ perm_error = 1;
+ continue;
+ }
+ len = APOL_PERMMAP_MAX_WEIGHT - perm_weight + 1;
+ if (len < APOL_PERMMAP_MIN_WEIGHT) {
+ len = APOL_PERMMAP_MIN_WEIGHT;
+ } else if (len > APOL_PERMMAP_MAX_WEIGHT) {
+ len = APOL_PERMMAP_MAX_WEIGHT;
+ }
+ if (perm_map & APOL_PERMMAP_READ) {
+ if (len < read_len && len <= max_len) {
+ found_read = 1;
+ read_len = len;
+ }
+ }
+ if (perm_map & APOL_PERMMAP_WRITE) {
+ if (len < write_len && len <= max_len) {
+ found_write = 1;
+ write_len = len;
+ }
+ }
+ }
+
+ /* if we have found any flows then connect them within the graph */
+ if ((found_read || found_write) &&
+ apol_infoflow_graph_connect_nodes(p, g, rule, types, found_read, read_len, found_write, write_len) < 0) {
+ goto cleanup;
+ }
+ if (perm_error) {
+ WARN(p, "%s", "Not all of the permissions found had associated permission maps.");
+ }
+
+ retval = 0;
+ cleanup:
+ qpol_iterator_destroy(&perm_iter);
+ return retval;
+}
+
+/**
+ * Given a vector of strings representing types, return a BST of
+ * qpol_type_t pointers consisting of those types, those types'
+ * attributes, and those types' aliases.
+ *
+ * @param p Policy within which to look up types,
+ * @param v Vector of type strings.
+ *
+ * @return BST of qpol_type_t pointers, or NULL on error. The caller
+ * is responsible for calling apol_bst_destroy() upon the returned
+ * value.
+ */
+static apol_bst_t *apol_infoflow_graph_create_required_types(const apol_policy_t * p, const apol_vector_t * v)
+{
+ apol_bst_t *types = NULL;
+ apol_vector_t *expanded_types = NULL;
+ size_t i;
+ char *s;
+ int retval = -1;
+ if ((types = apol_bst_create(NULL, NULL)) == NULL) {
+ ERR(p, "%s", strerror(errno));
+ goto cleanup;
+ }
+ for (i = 0; i < apol_vector_get_size(v); i++) {
+ s = (char *)apol_vector_get_element(v, i);
+ expanded_types = apol_query_create_candidate_type_list(p, s, 0, 1, APOL_QUERY_SYMBOL_IS_BOTH);
+ if (expanded_types == NULL) {
+ goto cleanup;
+ }
+ for (size_t j = 0; j < apol_vector_get_size(expanded_types); j++) {
+ qpol_type_t *t = (qpol_type_t *) apol_vector_get_element(expanded_types, j);
+ if (apol_bst_insert(types, t, NULL) < 0) {
+ ERR(p, "%s", strerror(errno));
+ goto cleanup;
+ }
+ }
+ apol_vector_destroy(&expanded_types);
+ }
+ retval = 0;
+ cleanup:
+ apol_vector_destroy(&expanded_types);
+ if (retval != 0) {
+ apol_bst_destroy(&types);
+ }
+ return types;
+}
+
+/**
+ * Determine if an av rule matches a list of qpol_type_t pointers.
+ * Both the source and target of the rule must be in the list.
+ *
+ * @param p Policy to which look up classes and permissions.
+ * @param rule AV rule to check.
+ * @param types BST of qpol_type_t, of which both the source and
+ * target types must be members. If NULL allow all types.
+ *
+ * @return 1 if rule matches, 0 if not, < 0 on error.
+ */
+static int apol_infoflow_graph_check_types(const apol_policy_t * p, const qpol_avrule_t * rule, const apol_bst_t * types)
+{
+ const qpol_type_t *source, *target;
+ void *result;
+ int retval = -1;
+ if (types == NULL) {
+ retval = 1;
+ goto cleanup;
+ }
+ if (qpol_avrule_get_source_type(p->p, rule, &source) < 0 || qpol_avrule_get_target_type(p->p, rule, &target) < 0) {
+ goto cleanup;
+ }
+ if (apol_bst_get_element(types, source, NULL, &result) < 0 || apol_bst_get_element(types, target, NULL, &result) < 0) {
+ retval = 0;
+ goto cleanup;
+ }
+ retval = 1;
+ cleanup:
+ return retval;
+}
+
+/**
+ * Determine if an av rule matches a list of apol_obj_perm_t. The
+ * rule's class must match at least one item in the list, and at least
+ * one of the rule's permissions must be on the list.
+ *
+ * @param p Policy to which look up classes and permissions.
+ * @param rule AV rule to check.
+ * @param class_perms Vector of apol_obj_perm_t, of which rule's class
+ * and permissions must be a member. If NULL or empty then allow all
+ * classes and permissions.
+ *
+ * @return 1 if rule matches, 0 if not, < 0 on error.
+ */
+static int apol_infoflow_graph_check_class_perms(const apol_policy_t * p, const qpol_avrule_t * rule,
+ const apol_vector_t * class_perms)
+{
+ const qpol_class_t *obj_class;
+ const char *obj_name;
+ char *perm;
+ qpol_iterator_t *iter = NULL;
+ apol_obj_perm_t *obj_perm = NULL;
+ apol_vector_t *obj_perm_v = NULL;
+ size_t i;
+ int retval = -1;
+
+ if (class_perms == NULL || apol_vector_get_size(class_perms) == 0) {
+ retval = 1;
+ goto cleanup;
+ }
+ if (qpol_avrule_get_object_class(p->p, rule, &obj_class) < 0 || qpol_class_get_name(p->p, obj_class, &obj_name) < 0) {
+ goto cleanup;
+ }
+ for (i = 0; i < apol_vector_get_size(class_perms); i++) {
+ obj_perm = (apol_obj_perm_t *) apol_vector_get_element(class_perms, i);
+ if (strcmp(apol_obj_perm_get_obj_name(obj_perm), obj_name) == 0) {
+ obj_perm_v = apol_obj_perm_get_perm_vector(obj_perm);
+ break;
+ }
+ }
+ if (i >= apol_vector_get_size(class_perms)) {
+ retval = 0; /* no matching class */
+ goto cleanup;
+ }
+ if (qpol_avrule_get_perm_iter(p->p, rule, &iter) < 0) {
+ goto cleanup;
+ }
+ for (; !qpol_iterator_end(iter); qpol_iterator_next(iter)) {
+ if (qpol_iterator_get_item(iter, (void **)&perm) < 0) {
+ goto cleanup;
+ }
+ if (apol_vector_get_index(obj_perm_v, perm, apol_str_strcmp, NULL, &i) == 0) {
+ free(perm);
+ retval = 1;
+ goto cleanup;
+ }
+ free(perm);
+ }
+ retval = 0; /* no matching perm */
+ cleanup:
+ qpol_iterator_destroy(&iter);
+ return retval;
+}
+
+/**
+ * Given a particular information flow analysis object, generate an
+ * infoflow graph relative to a particular policy. This graph is
+ * customized for the particular analysis.
+ *
+ * @param p Policy from which to create the infoflow graph.
+ * @param ia Parameters to tune the created graph.
+ * @param g Reference to where to store the graph. The caller is
+ * responsible for calling apol_infoflow_graph_destroy() upon this.
+ *
+ * @return 0 if the graph was created, < 0 on error. Upon error *g
+ * will be set to NULL.
+ */
+static int apol_infoflow_graph_create(const apol_policy_t * p, const apol_infoflow_analysis_t * ia, apol_infoflow_graph_t ** g)
+{
+ apol_bst_t *types = NULL;
+ qpol_iterator_t *iter = NULL;
+ int max_len = APOL_PERMMAP_MAX_WEIGHT - ia->min_weight + 1;
+ int compval, retval = -1;
+
+ *g = NULL;
+ if (p->pmap == NULL) {
+ ERR(p, "%s", "A permission map must be loaded prior to building the infoflow graph.");
+ goto cleanup;
+ }
+
+ INFO(p, "%s", "Generating information flow graph.");
+ if (ia->mode == APOL_INFOFLOW_MODE_TRANS && ia->intermed != NULL &&
+ (types = apol_infoflow_graph_create_required_types(p, ia->intermed)) == NULL) {
+ goto cleanup;
+ }
+
+ if ((*g = calloc(1, sizeof(**g))) == NULL ||
+ ((*g)->nodes_bst = apol_bst_create(apol_infoflow_node_compare, apol_infoflow_node_free)) == NULL) {
+ ERR(p, "%s", strerror(errno));
+ goto cleanup;
+ }
+ (*g)->mode = ia->mode;
+ (*g)->direction = ia->direction;
+ if (ia->result != NULL && ia->result[0] != '\0') {
+ if (((*g)->regex = malloc(sizeof(regex_t))) == NULL || regcomp((*g)->regex, ia->result, REG_EXTENDED | REG_NOSUB)) {
+ ERR(p, "%s", strerror(errno));
+ goto cleanup;
+ }
+ }
+ if (((*g)->edges = apol_vector_create(apol_infoflow_edge_free)) == NULL) {
+ ERR(p, "%s", strerror(errno));
+ goto cleanup;
+ }
+
+ if (qpol_policy_get_avrule_iter(p->p, QPOL_RULE_ALLOW, &iter) < 0) {
+ goto cleanup;
+ }
+
+ for (; !qpol_iterator_end(iter); qpol_iterator_next(iter)) {
+ qpol_avrule_t *rule;
+ if (qpol_iterator_get_item(iter, (void **)&rule) < 0) {
+ goto cleanup;
+ }
+ compval = apol_infoflow_graph_check_types(p, rule, types);
+ if (compval < 0) {
+ goto cleanup;
+ } else if (compval == 0) {
+ continue;
+ }
+ compval = apol_infoflow_graph_check_class_perms(p, rule, ia->class_perms);
+ if (compval < 0) {
+ goto cleanup;
+ } else if (compval == 0) {
+ continue;
+ }
+ if (apol_infoflow_graph_create_avrule(p, *g, rule, types, max_len) < 0) {
+ goto cleanup;
+ }
+ }
+
+ if (((*g)->nodes = apol_bst_get_vector((*g)->nodes_bst, 1)) == NULL) {
+ ERR(p, "%s", strerror(errno));
+ goto cleanup;
+ }
+ apol_bst_destroy(&(*g)->nodes_bst);
+ retval = 0;
+ cleanup:
+ apol_bst_destroy(&types);
+ qpol_iterator_destroy(&iter);
+ if (retval < 0) {
+ apol_infoflow_graph_destroy(g);
+ }
+ return retval;
+}
+
+void apol_infoflow_graph_destroy(apol_infoflow_graph_t ** g)
+{
+ if (g != NULL && *g != NULL) {
+ apol_bst_destroy(&(*g)->nodes_bst);
+ apol_vector_destroy(&(*g)->nodes);
+ apol_vector_destroy(&(*g)->edges);
+ apol_vector_destroy(&(*g)->further_start);
+ apol_vector_destroy(&(*g)->further_end);
+ apol_regex_destroy(&(*g)->regex);
+ free(*g);
+ *g = NULL;
+ }
+}
+
+/*************** infoflow graph direct analysis routines ***************/
+
+/**
+ * Given a graph and a target type, append to vector v all nodes
+ * (apol_infoflow_node_t) within the graph that use that type, one of
+ * that type's aliases, or one of that type's attributes. This will
+ * also implicitly permutate across all of the type's object classes.
+ *
+ * @param p Error reporting handler.
+ * @param g Information flow graph containing nodes.
+ * @param type Target type name to find.
+ * @param v Initialized vector to which append nodes.
+ *
+ * @return 0 on success, < 0 on error.
+ */
+static int apol_infoflow_graph_get_nodes_for_type(const apol_policy_t * p, const apol_infoflow_graph_t * g, const char *type,
+ apol_vector_t * v)
+{
+ size_t i, j;
+ apol_vector_t *cand_list = NULL;
+ int retval = -1;
+ if ((cand_list = apol_query_create_candidate_type_list(p, type, 0, 1, APOL_QUERY_SYMBOL_IS_BOTH)) == NULL) {
+ goto cleanup;
+ }
+ for (i = 0; i < apol_vector_get_size(g->nodes); i++) {
+ apol_infoflow_node_t *node;
+ node = (apol_infoflow_node_t *) apol_vector_get_element(g->nodes, i);
+ if (apol_vector_get_index(cand_list, node->type, NULL, NULL, &j) == 0 && apol_vector_append(v, node) < 0) {
+ goto cleanup;
+ }
+ }
+ retval = 0;
+ cleanup:
+ apol_vector_destroy(&cand_list);
+ return retval;
+}
+
+/**
+ * Return a usable infoflow result object. If there already exists a
+ * result object within vector v with the same start and ending type
+ * then reuse that object. Otherwise allocate and return a new
+ * infoflow result with its start and end type fields set.
+ *
+ * @param p Policy handler, for reporting errors.
+ * @param v Non-null vector of infoflow results.
+ * @param start_type Starting type for returned infoflow result object.
+ * @param end_type Starting type for returned infoflow result object.
+ *
+ * @return A usable infoflow result object, or NULL upon error.
+ */
+static apol_infoflow_result_t *apol_infoflow_direct_get_result(const apol_policy_t * p,
+ apol_vector_t * v, const qpol_type_t * start_type,
+ const qpol_type_t * end_type)
+{
+ size_t i;
+ apol_infoflow_result_t *r;
+ for (i = 0; i < apol_vector_get_size(v); i++) {
+ r = (apol_infoflow_result_t *) apol_vector_get_element(v, i);
+ if (r->start_type == start_type && r->end_type == end_type) {
+ return r;
+ }
+ }
+ if ((r = calloc(1, sizeof(*r))) == NULL || (r->steps = apol_vector_create(apol_infoflow_step_free)) == NULL
+ || apol_vector_append(v, r) < 0) {
+ ERR(p, "%s", strerror(ENOMEM));
+ infoflow_result_free(r);
+ return NULL;
+ }
+ r->start_type = start_type;
+ r->end_type = end_type;
+ r->length = INT_MAX;
+ return r;
+}
+
+/**
+ * Append the rules on an edge to a direct infoflow result.
+ *
+ * @param p Policy containing rules.
+ * @param edge Infoflow edge containing rules.
+ * @param direction Direction of flow, one of APOL_INFOFLOW_IN, etc.
+ * @param result Infoflow result to modify.
+ *
+ * @return 0 on success, < 0 on error.
+ */
+static int apol_infoflow_direct_define(const apol_policy_t * p,
+ const apol_infoflow_edge_t * edge, unsigned int direction, apol_infoflow_result_t * result)
+{
+ apol_infoflow_step_t *step = NULL;
+ if (apol_vector_get_size(result->steps) == 0) {
+ if ((step = calloc(1, sizeof(*step))) == NULL ||
+ (step->rules = apol_vector_create(NULL)) == NULL || apol_vector_append(result->steps, step) < 0) {
+ apol_infoflow_step_free(step);
+ ERR(p, "%s", strerror(ENOMEM));
+ return -1;
+ }
+ step->start_type = result->start_type;
+ step->end_type = result->end_type;
+ step->weight = 0;
+ } else {
+ step = (apol_infoflow_step_t *) apol_vector_get_element(result->steps, 0);
+ }
+ if (apol_vector_cat(step->rules, edge->rules) < 0) {
+ ERR(p, "%s", strerror(ENOMEM));
+ return -1;
+ }
+ result->direction |= direction;
+ //TODO: check that edge->lenght can be safely unsigned
+ if (edge->length < (int)result->length) {
+ result->length = edge->length;
+ }
+ return 0;
+}
+
+/**
+ * Given the regular expression compiled into the graph object and a
+ * type, determine if that regex matches that type or any of the
+ * type's aliases.
+ *
+ * @param p Policy containing type names.
+ * @param g Graph object containing regex.
+ * @param type Type to check against.
+ *
+ * @return 1 if comparison succeeds, 0 if not, < 0 on error.
+ */
+static int apol_infoflow_graph_compare(const apol_policy_t * p, apol_infoflow_graph_t * g, const qpol_type_t * type)
+{
+ const char *type_name;
+ qpol_iterator_t *alias_iter = NULL;
+ int compval = 0;
+ if (g->regex == NULL) {
+ return 1;
+ }
+ if (qpol_type_get_name(p->p, type, &type_name) < 0) {
+ return -1;
+ }
+ if (regexec(g->regex, type_name, 0, NULL, 0) == 0) {
+ return 1;
+ }
+ /* also check for matches against any of target's aliases */
+ if (qpol_type_get_alias_iter(p->p, type, &alias_iter) < 0) {
+ return -1;
+ }
+ for (; !qpol_iterator_end(alias_iter); qpol_iterator_next(alias_iter)) {
+ char *iter_name;
+ if (qpol_iterator_get_item(alias_iter, (void **)&iter_name) < 0) {
+ compval = -1;
+ break;
+ }
+ if (regexec(g->regex, iter_name, 0, NULL, 0) == 0) {
+ compval = 1;
+ break;
+ }
+ }
+ qpol_iterator_destroy(&alias_iter);
+ return compval;
+}
+
+/**
+ * For each result object in vector working_results, append a
+ * duplicate of it to vector results if (a) the infoflow analysis
+ * object direction is not BOTH or (b) the result object's direction
+ * is BOTH. Regardless of success or error, it is safe to destroy
+ * either vector without concern of double-free()ing things.
+ *
+ * @param p Policy handler, for reporting errors.
+ * @param working_results Vector of infoflow results to check.
+ * @param direction Direction of search.
+ * @param results Vector to which append duplicated infoflow results.
+ *
+ * @return 0 on success, < 0 on error.
+ */
+static int apol_infoflow_results_check_both(const apol_policy_t * p,
+ const apol_vector_t * working_results, unsigned int direction, apol_vector_t * results)
+{
+ size_t i;
+ apol_infoflow_result_t *r, *new_r;
+ for (i = 0; i < apol_vector_get_size(working_results); i++) {
+ r = (apol_infoflow_result_t *) apol_vector_get_element(working_results, i);
+ if (direction != APOL_INFOFLOW_BOTH || r->direction == APOL_INFOFLOW_BOTH) {
+ if ((new_r = calloc(1, sizeof(*new_r))) == NULL) {
+ ERR(p, "%s", strerror(ENOMEM));
+ return -1;
+ }
+ memcpy(new_r, r, sizeof(*new_r));
+ r->steps = NULL;
+ if (apol_vector_append(results, new_r) < 0) {
+ infoflow_result_free(new_r);
+ ERR(p, "%s", strerror(ENOMEM));
+ return -1;
+ }
+ }
+ }
+ return 0;
+}
+
+/**
+ * Given a start node, an edge, and flow direction, add an infoflow
+ * results to a vector. If the node on the other end of the edge is
+ * an attribute, first expand the attribute to its component types.
+ * If a regular expression is compiled into the infoflow graph, apply
+ * that regex match against candidate end node types prior to creating
+ * result nodes.
+ *
+ * @param p Policy to analyze.
+ * @param g Information flow graph to analyze.
+ * @param start_node Starting node.
+ * @param edge An edge from start_node.
+ * @param flow_dir Direction of search, either APOL_INFOFLOW_IN or
+ * APOL_INFOFLOW_OUT.
+ * @param results Non-NULL vector to which append infoflow results.
+ * The caller is responsible for calling apol_infoflow_results_free()
+ * upon each element afterwards.
+ *
+ * @return 0 on success, < 0 on error.
+ */
+static int apol_infoflow_analysis_direct_expand(const apol_policy_t * p,
+ apol_infoflow_graph_t * g,
+ apol_infoflow_node_t * start_node,
+ apol_infoflow_edge_t * edge, unsigned int flow_dir, apol_vector_t * results)
+{
+ apol_infoflow_node_t *end_node;
+ unsigned char isattr;
+ qpol_iterator_t *iter = NULL;
+ const qpol_type_t *type;
+ apol_infoflow_result_t *r;
+ int retval = -1, compval;
+
+ if (edge->start_node == start_node) {
+ end_node = edge->end_node;
+ } else {
+ end_node = edge->start_node;
+ }
+ if (qpol_type_get_isattr(p->p, end_node->type, &isattr) < 0) {
+ goto cleanup;
+ }
+ if (isattr) {
+ if (qpol_type_get_type_iter(p->p, end_node->type, &iter) < 0) {
+ goto cleanup;
+ }
+ if (qpol_iterator_end(iter)) {
+ retval = 0;
+ goto cleanup;
+ }
+ }
+ /* always do this loop once, either if end_node is an attribute or not */
+ do {
+ if (isattr) {
+ if (qpol_iterator_get_item(iter, (void **)&type) < 0) {
+ goto cleanup;
+ }
+ qpol_iterator_next(iter);
+ } else {
+ type = end_node->type;
+ }
+ compval = apol_infoflow_graph_compare(p, g, type);
+ if (compval < 0) {
+ goto cleanup;
+ } else if (compval == 0) {
+ continue;
+ }
+ if ((r = apol_infoflow_direct_get_result(p, results, start_node->type, type)) == NULL ||
+ apol_infoflow_direct_define(p, edge, flow_dir, r) < 0) {
+ goto cleanup;
+ }
+ } while (isattr && !qpol_iterator_end(iter));
+
+ retval = 0;
+ cleanup:
+ qpol_iterator_destroy(&iter);
+ return retval;
+}
+
+/**
+ * Perform a direct information flow analysis upon the given infoflow
+ * graph.
+ *
+ * @param p Policy to analyze.
+ * @param g Information flow graph to analyze.
+ * @param start_type Type from which to begin search.
+ * @param results Non-NULL vector to which append infoflow results.
+ * The caller is responsible for calling apol_infoflow_results_free()
+ * upon each element afterwards.
+ *
+ * @return 0 on success, < 0 on error.
+ */
+static int apol_infoflow_analysis_direct(const apol_policy_t * p,
+ apol_infoflow_graph_t * g, const char *start_type, apol_vector_t * results)
+{
+ apol_vector_t *nodes = NULL;
+ size_t i, j;
+ apol_infoflow_node_t *node;
+ apol_infoflow_edge_t *edge;
+ apol_vector_t *working_results = NULL;
+ int retval = -1;
+
+ if ((nodes = apol_vector_create(NULL)) == NULL || (working_results = apol_vector_create(infoflow_result_free)) == NULL) {
+ ERR(p, "%s", strerror(ENOMEM));
+ goto cleanup;
+ }
+ if (apol_infoflow_graph_get_nodes_for_type(p, g, start_type, nodes) < 0) {
+ goto cleanup;
+ }
+
+ if (g->direction == APOL_INFOFLOW_IN || g->direction == APOL_INFOFLOW_EITHER || g->direction == APOL_INFOFLOW_BOTH) {
+ for (i = 0; i < apol_vector_get_size(nodes); i++) {
+ node = (apol_infoflow_node_t *) apol_vector_get_element(nodes, i);
+ for (j = 0; j < apol_vector_get_size(node->in_edges); j++) {
+ edge = (apol_infoflow_edge_t *) apol_vector_get_element(node->in_edges, j);
+ if (apol_infoflow_analysis_direct_expand(p, g, node, edge, APOL_INFOFLOW_IN, working_results) < 0) {
+ goto cleanup;
+ }
+ }
+ }
+ }
+ if (g->direction == APOL_INFOFLOW_OUT || g->direction == APOL_INFOFLOW_EITHER || g->direction == APOL_INFOFLOW_BOTH) {
+ for (i = 0; i < apol_vector_get_size(nodes); i++) {
+ node = (apol_infoflow_node_t *) apol_vector_get_element(nodes, i);
+ for (j = 0; j < apol_vector_get_size(node->out_edges); j++) {
+ edge = (apol_infoflow_edge_t *) apol_vector_get_element(node->out_edges, j);
+ if (apol_infoflow_analysis_direct_expand(p, g, node, edge, APOL_INFOFLOW_OUT, working_results) < 0) {
+ goto cleanup;
+ }
+ }
+ }
+ }
+
+ if (apol_infoflow_results_check_both(p, working_results, g->direction, results) < 0) {
+ goto cleanup;
+ }
+
+ retval = 0;
+ cleanup:
+ apol_vector_destroy(&nodes);
+ apol_vector_destroy(&working_results);
+ return retval;
+}
+
+/*************** infoflow graph transitive analysis routines ***************/
+
+/**
+ * Prepare an infoflow graph for a transitive analysis by coloring its
+ * nodes and setting its parent and distance. For the start node
+ * color it red; for all others color them white.
+ *
+ * @param p Policy handler, for reporting errors.
+ * @param g Infoflow graph to initialize.
+ * @param start Node from which to begin analysis.
+ * @param q Queue of apol_infoflow_node_t pointers to which search.
+ *
+ * @return 0 on success, < 0 on error.
+ */
+static int apol_infoflow_graph_trans_init(const apol_policy_t * p,
+ apol_infoflow_graph_t * g, apol_infoflow_node_t * start, apol_queue_t * q)
+{
+ size_t i;
+ apol_infoflow_node_t *node;
+ for (i = 0; i < apol_vector_get_size(g->nodes); i++) {
+ node = (apol_infoflow_node_t *) apol_vector_get_element(g->nodes, i);
+ node->parent = NULL;
+ if (node == start) {
+ node->color = APOL_INFOFLOW_COLOR_RED;
+ node->distance = 0;
+ if (apol_queue_insert(q, node) < 0) {
+ ERR(p, "%s", strerror(ENOMEM));
+ return -1;
+ }
+ } else {
+ node->color = APOL_INFOFLOW_COLOR_WHITE;
+ node->distance = INT_MAX;
+ }
+ }
+ return 0;
+}
+
+/**
+ * Prepare an infoflow graph for furher transitive analysis by
+ * coloring its nodes and setting its parent and distance. For the
+ * start node color it grey; for all others color them white.
+ *
+ * @param p Policy handler, for reporting errors.
+ * @param g Infoflow graph to initialize.
+ * @param start Node from which to begin analysis.
+ * @param q Queue of apol_infoflow_node_t pointers to which search.
+ *
+ * @return 0 on success, < 0 on error.
+ */
+static int apol_infoflow_graph_trans_further_init(const apol_policy_t * p,
+ apol_infoflow_graph_t * g, apol_infoflow_node_t * start, apol_queue_t * q)
+{
+ size_t i;
+ apol_infoflow_node_t *node;
+ for (i = 0; i < apol_vector_get_size(g->nodes); i++) {
+ node = (apol_infoflow_node_t *) apol_vector_get_element(g->nodes, i);
+ node->parent = NULL;
+ if (node == start) {
+ node->color = APOL_INFOFLOW_COLOR_GREY;
+ node->distance = 0;
+ if (apol_queue_insert(q, node) < 0) {
+ ERR(p, "%s", strerror(ENOMEM));
+ return -1;
+ }
+ } else {
+ node->color = APOL_INFOFLOW_COLOR_WHITE;
+ node->distance = -1;
+ }
+ }
+ return 0;
+}
+
+/**
+ * Given a colored infoflow graph from apol_infoflow_analysis_trans(),
+ * find the shortest path from the end node to the start node.
+ * Allocate and return a vector of apol_infoflow_node_t that lists the
+ * nodes from the end to start.
+ *
+ * @param p Policy from which infoflow graph was generated.
+ * @param g Infoflow graph that has been colored.
+ * @param start_node Starting node for the path
+ * @param end_node Ending node to which to find a path.
+ * @param path Reference to a vector that will be allocated and filled
+ * with apol_infoflow_node_t pointers from the graph. The path will
+ * be in reverse order (i.e., from end node to a start node). Upon
+ * error this will be set to NULL.
+ *
+ * @return 0 on success, < 0 on error.
+ */
+static int apol_infoflow_trans_path(const apol_policy_t * p,
+ apol_infoflow_graph_t * g,
+ apol_infoflow_node_t * start_node, apol_infoflow_node_t * end_node, apol_vector_t ** path)
+{
+ int retval = -1;
+ apol_infoflow_node_t *next_node = end_node;
+ if ((*path = apol_vector_create(NULL)) == NULL) {
+ ERR(p, "%s", strerror(errno));
+ goto cleanup;
+ }
+ while (1) {
+ if (apol_vector_append(*path, next_node) < 0) {
+ ERR(p, "%s", strerror(errno));
+ goto cleanup;
+ }
+ if (next_node == start_node) {
+ break;
+ }
+ if (next_node == NULL || apol_vector_get_size(*path) >= apol_vector_get_size(g->nodes)) {
+ ERR(p, "%s", "Infinite loop in trans_path.");
+ errno = EPERM;
+ goto cleanup;
+ }
+ next_node = next_node->parent;
+ }
+ retval = 0;
+ cleanup:
+ if (retval != 0) {
+ apol_vector_destroy(path);
+ }
+ return retval;
+}
+
+/**
+ * Given a node within an infoflow graph, return the edge that
+ * connects it to next_node.
+ *
+ * @param p Policy handler, for reporting errors.
+ * @param g Infoflow graph from which to find edge.
+ * @param node Starting node.
+ * @param next_node Ending node.
+ *
+ * @return Edge connecting node to next_node, or NULL on error.
+ */
+static apol_infoflow_edge_t *apol_infoflow_trans_find_edge(const apol_policy_t * p,
+ apol_infoflow_graph_t * g,
+ apol_infoflow_node_t * node, apol_infoflow_node_t * next_node)
+{
+ apol_vector_t *v;
+ apol_infoflow_edge_t *edge;
+ size_t i;
+
+ if (g->direction == APOL_INFOFLOW_OUT) {
+ v = node->out_edges;
+ } else {
+ v = node->in_edges;
+ }
+ for (i = 0; i < apol_vector_get_size(v); i++) {
+ edge = (apol_infoflow_edge_t *) apol_vector_get_element(v, i);
+ if (g->direction == APOL_INFOFLOW_OUT) {
+ if (edge->start_node == node && edge->end_node == next_node) {
+ return edge;
+ }
+ } else {
+ if (edge->end_node == node && edge->start_node == next_node) {
+ return edge;
+ }
+
+ }
+ }
+ ERR(p, "%s", "Did not find an edge.");
+ return NULL;
+}
+
+/**
+ * Given a path of nodes, define a new infoflow result that represents
+ * that path. The given path is a list of nodes that must be in
+ * reverse order (i.e., from end node to start node) and must have at
+ * least 2 elements within.
+ *
+ * @param p Policy handler, for reporting errors.
+ * @param g Graph from which the node path originated.
+ * @param path Vector of apol_infoflow_node_t representing an infoflow
+ * path.
+ * @param end_type Ending type for the path.
+ * @param result Reference pointer to where to store result. The
+ * caller is responsible for calling apol_infoflow_result_free() upon
+ * the returned value. Upon error this will be set to NULL.
+ *
+ * @return 0 on success, < 0 on error.
+ */
+static int apol_infoflow_trans_define(const apol_policy_t * p,
+ apol_infoflow_graph_t * g,
+ apol_vector_t * path, const qpol_type_t * end_type, apol_infoflow_result_t ** result)
+{
+ apol_infoflow_step_t *step = NULL;
+ size_t path_len = apol_vector_get_size(path), i;
+ apol_infoflow_node_t *node, *next_node;
+ apol_infoflow_edge_t *edge;
+ int retval = -1, length = 0;
+ *result = NULL;
+
+ if (((*result) = calloc(1, sizeof(**result))) == NULL ||
+ ((*result)->steps = apol_vector_create_with_capacity(path_len, apol_infoflow_step_free)) == NULL) {
+ ERR(p, "%s", strerror(ENOMEM));
+ goto cleanup;
+ }
+ (*result)->end_type = end_type;
+ /* build in reverse order because path is from end node to
+ * start node */
+ node = (apol_infoflow_node_t *) apol_vector_get_element(path, path_len - 1);
+ (*result)->start_type = node->type;
+ (*result)->direction = g->direction;
+ for (i = path_len - 1; i > 0; i--, node = next_node) {
+ next_node = (apol_infoflow_node_t *) apol_vector_get_element(path, i - 1);
+ edge = apol_infoflow_trans_find_edge(p, g, node, next_node);
+ if (edge == NULL) {
+ goto cleanup;
+ }
+ length += edge->length;
+ if ((step = calloc(1, sizeof(*step))) == NULL ||
+ (step->rules = apol_vector_create_from_vector(edge->rules, NULL, NULL, NULL)) == NULL ||
+ apol_vector_append((*result)->steps, step) < 0) {
+ apol_infoflow_step_free(step);
+ ERR(p, "%s", strerror(ENOMEM));
+ return -1;
+ }
+ step->start_type = edge->start_node->type;
+ step->end_type = edge->end_node->type;
+ step->weight = APOL_PERMMAP_MAX_WEIGHT - edge->length + 1;
+ }
+ (*result)->length = length;
+ retval = 0;
+ cleanup:
+ if (retval != 0) {
+ infoflow_result_free(*result);
+ *result = NULL;
+ }
+ return retval;
+}
+
+/**
+ * Compares two apol_infoflow_step_t objects, returning 0 if they have
+ * the same contents, non-zero or not. This is a callback function to
+ * apol_vector_compare().
+ *
+ * @param a First apol_infoflow_step_t to compare.
+ * @param b Other apol_infoflow_step_t to compare.
+ * @param data Unused.
+ *
+ * @return 0 if the steps are the same, non-zero if different.
+ */
+static int apol_infoflow_trans_step_comp(const void *a, const void *b, void *data __attribute__ ((unused)))
+{
+ const apol_infoflow_step_t *step_a = (const apol_infoflow_step_t *)a;
+ const apol_infoflow_step_t *step_b = (const apol_infoflow_step_t *)b;
+ size_t i;
+ if (step_a->start_type != step_b->start_type) {
+ return (int)((char *)step_a->start_type - (char *)step_b->start_type);
+ }
+ if (step_a->end_type != step_b->end_type) {
+ return (int)((char *)step_a->end_type - (char *)step_b->end_type);
+ }
+ return apol_vector_compare(step_a->rules, step_b->rules, NULL, NULL, &i);
+}
+
+/**
+ * Given a path, append to the results vector a new
+ * apol_infoflow_result object - but only if there is not already a
+ * result describing the same path.
+ *
+ * @param p Policy handler, for reporting errors.
+ * @param g Infoflow graph to which create results.
+ * @param path Vector of apol_infoflow_node_t describing a path from
+ * an end node to a starting node.
+ * @param end_type Ending type for the path.
+ * @param results Vector of apol_infoflow_result_t to possibly append
+ * a new result.
+ *
+ * @return 0 on success, < 0 on error.
+ */
+static int apol_infoflow_trans_append(const apol_policy_t * p,
+ apol_infoflow_graph_t * g,
+ apol_vector_t * path, const qpol_type_t * end_type, apol_vector_t * results)
+{
+ apol_infoflow_result_t *new_r = NULL, *r;
+ size_t i, j;
+ int compval, retval = -1;
+
+ if (apol_infoflow_trans_define(p, g, path, end_type, &new_r) < 0) {
+ goto cleanup;
+ }
+
+ /* First we look for duplicate paths */
+ for (i = 0; i < apol_vector_get_size(results); i++) {
+ r = (apol_infoflow_result_t *) apol_vector_get_element(results, i);
+ if (r->end_type != end_type ||
+ r->direction != new_r->direction || apol_vector_get_size(r->steps) != apol_vector_get_size(new_r->steps)) {
+ break;
+ }
+ compval = apol_vector_compare(r->steps, new_r->steps, apol_infoflow_trans_step_comp, NULL, &j);
+ /* found a dup TODO - make certain all of the object
+ * class / rules are kept */
+ if (compval == 0) {
+ infoflow_result_free(new_r);
+ new_r = NULL;
+ retval = 0;
+ goto cleanup;
+ }
+ }
+
+ /* If we are here the newly built path is unique. */
+ if (apol_vector_append(results, new_r) < 0) {
+ goto cleanup;
+ }
+ retval = 0;
+ cleanup:
+ if (retval != 0) {
+ infoflow_result_free(new_r);
+ }
+ return retval;
+}
+
+/**
+ * Given a start and end node, add a trans infoflow results to a
+ * vector. If a regular expression is compiled into the infoflow
+ * graph, apply that regex match against candidate end node types
+ * prior to creating result nodes.
+ *
+ * @param p Policy to analyze.
+ * @param g Information flow graph to analyze.
+ * @param start_node Starting node.
+ * @param end_node Ending node.
+ * @param results Non-NULL vector to which append infoflow result.
+ * The caller is responsible for calling apol_infoflow_results_free()
+ * upon each element afterwards.
+ *
+ * @return 0 on success (including no result actually added), or < 0
+ * on error.
+ */
+static int apol_infoflow_analysis_trans_expand(const apol_policy_t * p,
+ apol_infoflow_graph_t * g,
+ apol_infoflow_node_t * start_node,
+ apol_infoflow_node_t * end_node, apol_vector_t * results)
+{
+ unsigned char isattr;
+ apol_vector_t *path = NULL;
+ int retval = -1, compval;
+
+ if (qpol_type_get_isattr(p->p, end_node->type, &isattr) < 0) {
+ goto cleanup;
+ }
+ assert(isattr == 0);
+ if (start_node->type == end_node->type) {
+ return 0;
+ }
+ compval = apol_infoflow_graph_compare(p, g, end_node->type);
+ if (compval < 0) {
+ goto cleanup;
+ } else if (compval == 0) {
+ return 0;
+ }
+ if (apol_infoflow_trans_path(p, g, start_node, end_node, &path) < 0 ||
+ apol_infoflow_trans_append(p, g, path, end_node->type, results) < 0) {
+ goto cleanup;
+ }
+ retval = 0;
+ cleanup:
+ apol_vector_destroy(&path);
+ return retval;
+}
+
+/**
+ * Perform a transitive information flow analysis upon the given
+ * infoflow graph starting from some particular node within the graph.
+ *
+ * This is a label correcting shortest path algorithm; see Bertsekas,
+ * D. P., "A Simple and Fast Label Correcting Algorithm for Shortest
+ * Paths," Networks, Vol. 23, pp. 703-709, 1993. for more information.
+ * A label correcting algorithm is needed instead of the more common
+ * Dijkstra label setting algorithm to correctly handle the the cycles
+ * that are possible in these graphs.
+ *
+ * This algorithm finds the shortest path between a given start node
+ * and all other nodes in the graph. Any paths that it finds it
+ * appends to the iflow_transitive_t structure. This is a basic label
+ * correcting algorithm with 1 optimization. It uses the D'Esopo-Pape
+ * method for node selection in the node queue. Why is this faster?
+ * The paper referenced above says "No definitive explanation has been
+ * given." They have fancy graphs to show that it is faster though
+ * and the important part is that the worst case isn't much worse that
+ * N^2 - much better than an n^3 transitive closure. Additionally,
+ * most normal sparse graphs are significantly better than the worst
+ * case.
+ *
+ * @param p Policy to analyze.
+ * @param g Information flow graph to analyze.
+ * @param start Node from which to begin search.
+ * @param results Non-NULL vector to which append infoflow results.
+ * The caller is responsible for calling apol_infoflow_results_free()
+ * upon each element afterwards.
+ *
+ * @return 0 on success, < 0 on error.
+ */
+static int apol_infoflow_analysis_trans_shortest_path(const apol_policy_t * p,
+ apol_infoflow_graph_t * g,
+ apol_infoflow_node_t * start, apol_vector_t * results)
+{
+ apol_vector_t *edge_list;
+ apol_queue_t *queue = NULL;
+ apol_infoflow_node_t *node, *cur_node;
+ apol_infoflow_edge_t *edge;
+ size_t i;
+ int retval = -1;
+
+ if ((queue = apol_queue_create()) == NULL) {
+ ERR(p, "%s", strerror(ENOMEM));
+ goto cleanup;
+ }
+ if (apol_infoflow_graph_trans_init(p, g, start, queue) < 0) {
+ goto cleanup;
+ }
+
+ while ((cur_node = apol_queue_remove(queue)) != NULL) {
+ cur_node->color = APOL_INFOFLOW_COLOR_GREY;
+ if (g->direction == APOL_INFOFLOW_OUT) {
+ edge_list = cur_node->out_edges;
+ } else {
+ edge_list = cur_node->in_edges;
+ }
+ for (i = 0; i < apol_vector_get_size(edge_list); i++) {
+ edge = (apol_infoflow_edge_t *) apol_vector_get_element(edge_list, i);
+ if (g->direction == APOL_INFOFLOW_OUT) {
+ node = edge->end_node;
+ } else {
+ node = edge->start_node;
+ }
+ if (node == start) {
+ continue;
+ }
+
+ if (node->distance > cur_node->distance + edge->length) {
+ node->distance = cur_node->distance + edge->length;
+ node->parent = cur_node;
+ /* If this node has been inserted into
+ * the queue before insert it at the
+ * beginning, otherwise it goes to the
+ * end. See the comment at the
+ * beginning of the function for
+ * why. */
+ if (node->color != APOL_INFOFLOW_COLOR_RED) {
+ if (node->color == APOL_INFOFLOW_COLOR_GREY) {
+ if (apol_queue_push(queue, node) < 0) {
+ ERR(p, "%s", strerror(ENOMEM));
+ goto cleanup;
+ }
+ } else {
+ if (apol_queue_insert(queue, node) < 0) {
+ ERR(p, "%s", strerror(ENOMEM));
+ goto cleanup;
+ }
+ }
+ node->color = APOL_INFOFLOW_COLOR_RED;
+ }
+ }
+ }
+ }
+
+ /* Find all of the paths and add them to the results vector */
+ for (i = 0; i < apol_vector_get_size(g->nodes); i++) {
+ cur_node = (apol_infoflow_node_t *) apol_vector_get_element(g->nodes, i);
+ if (cur_node->parent == NULL || cur_node == start) {
+ continue;
+ }
+ if (apol_infoflow_analysis_trans_expand(p, g, start, cur_node, results) < 0) {
+ goto cleanup;
+ }
+ }
+
+ retval = 0;
+ cleanup:
+ apol_queue_destroy(&queue);
+ return retval;
+}
+
+/**
+ * Perform a transitive information flow analysis upon the given
+ * infoflow graph.
+ *
+ * @param p Policy to analyze.
+ * @param g Information flow graph to analyze.
+ * @param start_type Type from which to begin search.
+ * @param results Non-NULL vector to which append infoflow results.
+ * The caller is responsible for calling apol_infoflow_results_free()
+ * upon each element afterwards.
+ *
+ * @return 0 on success, < 0 on error.
+ */
+static int apol_infoflow_analysis_trans(const apol_policy_t * p,
+ apol_infoflow_graph_t * g, const char *start_type, apol_vector_t * results)
+{
+ apol_vector_t *start_nodes = NULL;
+ apol_infoflow_node_t *start_node;
+ size_t i;
+ int retval = -1;
+
+ if (g->direction != APOL_INFOFLOW_IN && g->direction != APOL_INFOFLOW_OUT) {
+ ERR(p, "%s", strerror(EINVAL));
+ goto cleanup;
+ }
+ if ((start_nodes = apol_vector_create(NULL)) == NULL) {
+ ERR(p, "%s", strerror(errno));
+ goto cleanup;
+ }
+ if (apol_infoflow_graph_get_nodes_for_type(p, g, start_type, start_nodes) < 0) {
+ goto cleanup;
+ }
+ for (i = 0; i < apol_vector_get_size(start_nodes); i++) {
+ start_node = (apol_infoflow_node_t *) apol_vector_get_element(start_nodes, i);
+ if (apol_infoflow_analysis_trans_shortest_path(p, g, start_node, results) < 0) {
+ goto cleanup;
+ }
+ }
+ retval = 0;
+ cleanup:
+ apol_vector_destroy(&start_nodes);
+ return retval;
+}
+
+/**
+ * Given a vector, allocate and return a new vector with the elements
+ * shuffled about. This will make a shallow copy of the original
+ * vector's elements.
+ *
+ * @param p Policy handler, for error reporting.
+ * @param g Transitive infoflow graph containing PRNG object.
+ * @param v Vector to shuffle.
+ *
+ * @return A newly allocated vector with shuffled elements, or NULL
+ * upon error. The caller must call apol_vector_destroy() upon the
+ * returned value.
+ */
+static apol_vector_t *apol_infoflow_trans_further_shuffle(const apol_policy_t * p, apol_infoflow_graph_t * g, apol_vector_t * v)
+{
+ size_t i, j, size;
+ void **deck = NULL, *tmp;
+ apol_vector_t *new_v = NULL;
+ int retval = -1;
+ size = apol_vector_get_size(v);
+ if ((new_v = apol_vector_create_with_capacity(size, NULL)) == NULL) {
+ ERR(p, "%s", strerror(errno));
+ goto cleanup;
+ }
+ if (size == 0) {
+ retval = 0;
+ goto cleanup;
+ }
+ if ((deck = malloc(size * sizeof(*deck))) == NULL) {
+ ERR(p, "%s", strerror(errno));
+ goto cleanup;
+ }
+ for (i = 0; i < size; i++) {
+ deck[i] = apol_vector_get_element(v, i);
+ }
+ for (i = size - 1; i > 0; i--) {
+ j = (size_t) ((apol_infoflow_rand(g) / (RAND_MAX + 1.0)) * i);
+ tmp = deck[i];
+ deck[i] = deck[j];
+ deck[j] = tmp;
+ }
+ for (i = 0; i < size; i++) {
+ if (apol_vector_append(new_v, deck[i]) < 0) {
+ ERR(p, "%s", strerror(ENOMEM));
+ goto cleanup;
+ }
+ }
+ retval = 0;
+ cleanup:
+ free(deck);
+ if (retval != 0) {
+ apol_vector_destroy(&new_v);
+ }
+ return new_v;
+}
+
+static int apol_infoflow_analysis_trans_further(const apol_policy_t * p,
+ apol_infoflow_graph_t * g, apol_infoflow_node_t * start, apol_vector_t * results)
+{
+ apol_vector_t *edge_list = NULL;
+ apol_queue_t *queue = NULL;
+ apol_infoflow_node_t *node, *cur_node;
+ apol_infoflow_edge_t *edge;
+ size_t i;
+ int retval = -1;
+
+ if ((queue = apol_queue_create()) == NULL) {
+ ERR(p, "%s", strerror(ENOMEM));
+ goto cleanup;
+ }
+ if (apol_infoflow_graph_trans_further_init(p, g, start, queue) < 0) {
+ goto cleanup;
+ }
+
+ while ((cur_node = apol_queue_remove(queue)) != NULL) {
+ if (cur_node != start &&
+ apol_vector_get_index(g->further_end, cur_node, NULL, NULL, &i) == 0 &&
+ apol_infoflow_analysis_trans_expand(p, g, start, cur_node, results) < 0) {
+ goto cleanup;
+ }
+ cur_node->color = APOL_INFOFLOW_COLOR_BLACK;
+ if (g->direction == APOL_INFOFLOW_OUT) {
+ edge_list = cur_node->out_edges;
+ } else {
+ edge_list = cur_node->in_edges;
+ }
+ edge_list = apol_infoflow_trans_further_shuffle(p, g, edge_list);
+ if (edge_list == NULL) {
+ goto cleanup;
+ }
+ for (i = 0; i < apol_vector_get_size(edge_list); i++) {
+ edge = (apol_infoflow_edge_t *) apol_vector_get_element(edge_list, i);
+ if (g->direction == APOL_INFOFLOW_OUT) {
+ node = edge->end_node;
+ } else {
+ node = edge->start_node;
+ }
+ if (node->color == APOL_INFOFLOW_COLOR_WHITE) {
+ node->color = APOL_INFOFLOW_COLOR_GREY;
+ node->distance = cur_node->distance + 1;
+ node->parent = cur_node;
+ if (apol_queue_push(queue, node) < 0) {
+ ERR(p, "%s", strerror(ENOMEM));
+ goto cleanup;
+ }
+ }
+ }
+ apol_vector_destroy(&edge_list);
+ }
+ retval = 0;
+ cleanup:
+ apol_vector_destroy(&edge_list);
+ apol_queue_destroy(&queue);
+ return retval;
+}
+
+/******************** infoflow analysis object routines ********************/
+
+int apol_infoflow_analysis_do(const apol_policy_t * p, const apol_infoflow_analysis_t * ia, apol_vector_t ** v,
+ apol_infoflow_graph_t ** g)
+{
+ int retval = -1;
+ if (v != NULL) {
+ *v = NULL;
+ }
+ if (g != NULL) {
+ *g = NULL;
+ }
+ if (p == NULL || ia == NULL || v == NULL || g == NULL || ia->mode == 0 || ia->direction == 0) {
+ ERR(p, "%s", strerror(EINVAL));
+ goto cleanup;
+ }
+ if (apol_infoflow_graph_create(p, ia, g) < 0) {
+ goto cleanup;
+ }
+ INFO(p, "%s", "Searching information flow graph.");
+ retval = apol_infoflow_analysis_do_more(p, *g, ia->type, v);
+ cleanup:
+ if (retval != 0) {
+ apol_infoflow_graph_destroy(g);
+ }
+ return retval;
+}
+
+int apol_infoflow_analysis_do_more(const apol_policy_t * p, apol_infoflow_graph_t * g, const char *type, apol_vector_t ** v)
+{
+ const qpol_type_t *start_type;
+ int retval = -1;
+ if (v != NULL) {
+ *v = NULL;
+ }
+ if (p == NULL || g == NULL || type == NULL || v == NULL) {
+ ERR(p, "%s", strerror(EINVAL));
+ goto cleanup;
+ }
+
+ if (apol_query_get_type(p, type, &start_type) < 0) {
+ goto cleanup;
+ }
+
+ if ((*v = apol_vector_create(infoflow_result_free)) == NULL) {
+ ERR(p, "%s", strerror(ENOMEM));
+ goto cleanup;
+ }
+
+ if ((g->mode == APOL_INFOFLOW_MODE_DIRECT &&
+ apol_infoflow_analysis_direct(p, g, type, *v) < 0) ||
+ (g->mode == APOL_INFOFLOW_MODE_TRANS && apol_infoflow_analysis_trans(p, g, type, *v) < 0)) {
+ goto cleanup;
+ }
+
+ retval = 0;
+ cleanup:
+ if (retval != 0) {
+ apol_vector_destroy(v);
+ }
+ return retval;
+}
+
+int apol_infoflow_analysis_trans_further_prepare(const apol_policy_t * p,
+ apol_infoflow_graph_t * g, const char *start_type, const char *end_type)
+{
+ const qpol_type_t *stype, *etype;
+ int retval = -1;
+
+ apol_infoflow_srand(g);
+ if (apol_query_get_type(p, start_type, &stype) < 0 || apol_query_get_type(p, end_type, &etype) < 0) {
+ goto cleanup;
+ }
+ if (g->mode != APOL_INFOFLOW_MODE_TRANS) {
+ ERR(p, "%s", "May only perform further infoflow analysis when the graph is transitive.");
+ goto cleanup;
+ }
+ apol_vector_destroy(&g->further_start);
+ apol_vector_destroy(&g->further_end);
+ if ((g->further_start = apol_vector_create(NULL)) == NULL || (g->further_end = apol_vector_create(NULL)) == NULL) {
+ ERR(p, "%s", strerror(errno));
+ goto cleanup;
+ }
+ if (apol_infoflow_graph_get_nodes_for_type(p, g, start_type, g->further_start) < 0 ||
+ apol_infoflow_graph_get_nodes_for_type(p, g, end_type, g->further_end) < 0) {
+ goto cleanup;
+ }
+ g->current_start = 0;
+ retval = 0;
+ cleanup:
+ return retval;
+}
+
+int apol_infoflow_analysis_trans_further_next(const apol_policy_t * p, apol_infoflow_graph_t * g, apol_vector_t ** v)
+{
+ apol_infoflow_node_t *start_node;
+ int retval = -1;
+ if (p == NULL || g == NULL || v == NULL) {
+ ERR(p, "%s", strerror(EINVAL));
+ errno = EINVAL;
+ return -1;
+ }
+ if (*v == NULL) {
+ *v = apol_vector_create(infoflow_result_free);
+ }
+ if (g->further_start == NULL) {
+ ERR(p, "%s", "Infoflow graph was not prepared yet.");
+ goto cleanup;
+ }
+ start_node = apol_vector_get_element(g->further_start, g->current_start);
+ if (apol_infoflow_analysis_trans_further(p, g, start_node, *v) < 0) {
+ goto cleanup;
+ }
+ g->current_start++;
+ if (g->current_start >= apol_vector_get_size(g->further_start)) {
+ g->current_start = 0;
+ }
+ retval = 0;
+ cleanup:
+ return retval;
+}
+
+apol_infoflow_analysis_t *apol_infoflow_analysis_create(void)
+{
+ return calloc(1, sizeof(apol_infoflow_analysis_t));
+}
+
+void apol_infoflow_analysis_destroy(apol_infoflow_analysis_t ** ia)
+{
+ if (*ia != NULL) {
+ free((*ia)->type);
+ free((*ia)->result);
+ apol_vector_destroy(&(*ia)->intermed);
+ apol_vector_destroy(&(*ia)->class_perms);
+ free(*ia);
+ *ia = NULL;
+ }
+}
+
+int apol_infoflow_analysis_set_mode(const apol_policy_t * p, apol_infoflow_analysis_t * ia, unsigned int mode)
+{
+ switch (mode) {
+ case APOL_INFOFLOW_MODE_DIRECT:
+ case APOL_INFOFLOW_MODE_TRANS:
+ {
+ ia->mode = mode;
+ break;
+ }
+ default:
+ {
+ ERR(p, "%s", strerror(EINVAL));
+ return -1;
+ }
+ }
+ return 0;
+}
+
+int apol_infoflow_analysis_set_dir(const apol_policy_t * p, apol_infoflow_analysis_t * ia, unsigned int dir)
+{
+ switch (dir) {
+ case APOL_INFOFLOW_IN:
+ case APOL_INFOFLOW_OUT:
+ case APOL_INFOFLOW_BOTH:
+ case APOL_INFOFLOW_EITHER:
+ {
+ ia->direction = dir;
+ break;
+ }
+ default:
+ {
+ ERR(p, "%s", strerror(EINVAL));
+ return -1;
+ }
+ }
+ return 0;
+}
+
+int apol_infoflow_analysis_set_type(const apol_policy_t * p, apol_infoflow_analysis_t * ia, const char *name)
+{
+ if (name == NULL) {
+ ERR(p, "%s", strerror(EINVAL));
+ return -1;
+ }
+ return apol_query_set(p, &ia->type, NULL, name);
+}
+
+static int compare_class_perm_by_class_name(const void *in_op, const void *class_name, void *unused __attribute__ ((unused)))
+{
+ const apol_obj_perm_t *op = (const apol_obj_perm_t *)in_op;
+ const char *name = (const char *)class_name;
+
+ return strcmp(apol_obj_perm_get_obj_name(op), name);
+}
+
+int apol_infoflow_analysis_append_intermediate(const apol_policy_t * policy, apol_infoflow_analysis_t * ia, const char *type)
+{
+ char *tmp = NULL;
+ if (type == NULL) {
+ apol_vector_destroy(&ia->intermed);
+ return 0;
+ }
+ if (ia->intermed == NULL && (ia->intermed = apol_vector_create(free)) == NULL) {
+ ERR(policy, "Error appending type to analysis: %s", strerror(ENOMEM));
+ return -1;
+ }
+ if ((tmp = strdup(type)) == NULL || apol_vector_append(ia->intermed, tmp) < 0) {
+ free(tmp);
+ ERR(policy, "Error appending type to analysis: %s", strerror(ENOMEM));
+ return -1;
+ }
+ return 0;
+}
+
+int apol_infoflow_analysis_append_class_perm(const apol_policy_t * p,
+ apol_infoflow_analysis_t * ia, const char *class_name, const char *perm_name)
+{
+ apol_obj_perm_t *op = NULL;
+ size_t i;
+
+ if (p == NULL || ia == NULL) {
+ ERR(p, "%s", strerror(EINVAL));
+ errno = EINVAL;
+ return -1;
+ }
+ if (class_name == NULL) {
+ apol_vector_destroy(&ia->class_perms);
+ return 0;
+ }
+ if (perm_name == NULL) {
+ ERR(p, "%s", strerror(EINVAL));
+ errno = EINVAL;
+ return -1;
+ }
+ if (ia->class_perms == NULL && (ia->class_perms = apol_vector_create(apol_obj_perm_free)) == NULL) {
+ ERR(p, "%s", strerror(errno));
+ return -1;
+ }
+
+ if (apol_vector_get_index(ia->class_perms, (void *)class_name, compare_class_perm_by_class_name, NULL, &i) < 0) {
+ if (perm_name) {
+ if ((op = apol_obj_perm_create()) == NULL) {
+ ERR(p, "%s", strerror(errno));
+ return -1;
+ }
+ if (apol_obj_perm_set_obj_name(op, class_name) ||
+ apol_obj_perm_append_perm(op, perm_name) || apol_vector_append(ia->class_perms, op)) {
+ ERR(p, "%s", strerror(errno));
+ apol_obj_perm_free(op);
+ return -1;
+ }
+ } else {
+ return 0; /* nothing to clear; done */
+ }
+ } else {
+ op = apol_vector_get_element(ia->class_perms, i);
+ if (apol_obj_perm_append_perm(op, perm_name)) {
+ ERR(p, "%s", strerror(errno));
+ return -1;
+ }
+ }
+ return 0;
+}
+
+int apol_infoflow_analysis_set_min_weight(const apol_policy_t * p
+ __attribute__ ((unused)), apol_infoflow_analysis_t * ia, int min_weight)
+{
+ if (min_weight <= 0) {
+ ia->min_weight = 0;
+ } else if (min_weight >= APOL_PERMMAP_MAX_WEIGHT) {
+ ia->min_weight = APOL_PERMMAP_MAX_WEIGHT;
+ } else {
+ ia->min_weight = min_weight;
+ }
+ return 0;
+}
+
+int apol_infoflow_analysis_set_result_regex(const apol_policy_t * p, apol_infoflow_analysis_t * ia, const char *result)
+{
+ return apol_query_set(p, &ia->result, NULL, result);
+}
+
+/*************** functions to access infoflow results ***************/
+
+unsigned int apol_infoflow_result_get_dir(const apol_infoflow_result_t * result)
+{
+ if (!result) {
+ errno = EINVAL;
+ return 0;
+ }
+ return result->direction;
+}
+
+const qpol_type_t *apol_infoflow_result_get_start_type(const apol_infoflow_result_t * result)
+{
+ if (!result) {
+ errno = EINVAL;
+ return NULL;
+ }
+ return result->start_type;
+}
+
+const qpol_type_t *apol_infoflow_result_get_end_type(const apol_infoflow_result_t * result)
+{
+ if (!result) {
+ errno = EINVAL;
+ return NULL;
+ }
+ return result->end_type;
+}
+
+unsigned int apol_infoflow_result_get_length(const apol_infoflow_result_t * result)
+{
+ if (!result) {
+ errno = EINVAL;
+ return 0;
+ }
+ assert(result->length != 0);
+ return result->length;
+}
+
+const apol_vector_t *apol_infoflow_result_get_steps(const apol_infoflow_result_t * result)
+{
+ if (!result) {
+ errno = EINVAL;
+ return NULL;
+ }
+ return result->steps;
+}
+
+const qpol_type_t *apol_infoflow_step_get_start_type(const apol_infoflow_step_t * step)
+{
+ if (!step) {
+ errno = EINVAL;
+ return NULL;
+ }
+ return step->start_type;
+}
+
+const qpol_type_t *apol_infoflow_step_get_end_type(const apol_infoflow_step_t * step)
+{
+ if (!step) {
+ errno = EINVAL;
+ return NULL;
+ }
+ return step->end_type;
+}
+
+int apol_infoflow_step_get_weight(const apol_infoflow_step_t * step)
+{
+ if (!step) {
+ errno = EINVAL;
+ return -1;
+ }
+ return step->weight;
+}
+
+const apol_vector_t *apol_infoflow_step_get_rules(const apol_infoflow_step_t * step)
+{
+ if (!step) {
+ errno = EINVAL;
+ return NULL;
+ }
+ return step->rules;
+}
+
+/******************** protected functions ********************/
+
+apol_infoflow_result_t *infoflow_result_create_from_infoflow_result(const apol_infoflow_result_t * result)
+{
+ apol_infoflow_result_t *new_r = NULL;
+ apol_infoflow_step_t *step, *new_step;
+ size_t i;
+ int retval = -1;
+
+ if ((new_r = calloc(1, sizeof(*new_r))) == NULL ||
+ (new_r->steps = apol_vector_create_with_capacity(apol_vector_get_size(result->steps), apol_infoflow_step_free)) == NULL)
+ {
+ goto cleanup;
+ }
+ new_r->start_type = result->start_type;
+ new_r->end_type = result->end_type;
+ new_r->direction = result->direction;
+ new_r->length = result->length;
+ for (i = 0; i < apol_vector_get_size(result->steps); i++) {
+ step = (apol_infoflow_step_t *) apol_vector_get_element(result->steps, i);
+ if ((new_step = calloc(1, sizeof(*new_step))) == NULL ||
+ (new_step->rules = apol_vector_create_from_vector(step->rules, NULL, NULL, NULL)) == NULL ||
+ apol_vector_append(new_r->steps, new_step) < 0) {
+ apol_infoflow_step_free(new_step);
+ goto cleanup;
+ }
+ new_step->start_type = step->start_type;
+ new_step->end_type = step->end_type;
+ new_step->weight = step->weight;
+ }
+ retval = 0;
+ cleanup:
+ if (retval != 0) {
+ infoflow_result_free(new_r);
+ return NULL;
+ }
+ return new_r;
+}
+
+void infoflow_result_free(void *result)
+{
+ if (result != NULL) {
+ apol_infoflow_result_t *r = (apol_infoflow_result_t *) result;
+ apol_vector_destroy(&r->steps);
+ free(r);
+ }
+}