Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ tests/check_startup
tests/check_te_checks
tests/check_ordering
tests/check_perm_macro
tests/check_infer
tests/functional/policies/parse_errors/test3_tmp.if
tests/functional/policies/parse_errors/test5_tmp.te
tests/functional/policies/parse_errors/test6_tmp.if
Expand Down
2 changes: 1 addition & 1 deletion src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# limitations under the License.

bin_PROGRAMS = selint
selint_SOURCES = main.c lex.l parse.y tree.c tree.h selint_error.h parse_functions.c parse_functions.h maps.c maps.h runner.c runner.h parse_fc.c parse_fc.h template.c template.h file_list.c file_list.h check_hooks.c check_hooks.h fc_checks.c fc_checks.h util.c util.h if_checks.c if_checks.h selint_config.c selint_config.h string_list.c string_list.h startup.c startup.h te_checks.c te_checks.h ordering.c ordering.h color.c color.h perm_macro.c perm_macro.h
selint_SOURCES = main.c lex.l parse.y tree.c tree.h selint_error.h parse_functions.c parse_functions.h maps.c maps.h runner.c runner.h parse_fc.c parse_fc.h template.c template.h file_list.c file_list.h check_hooks.c check_hooks.h fc_checks.c fc_checks.h util.c util.h if_checks.c if_checks.h selint_config.c selint_config.h string_list.c string_list.h startup.c startup.h te_checks.c te_checks.h ordering.c ordering.h color.c color.h perm_macro.c perm_macro.h infer.c infer.h
BUILT_SOURCES = parse.h
AM_YFLAGS = -d -Wno-yacc -Werror=conflicts-rr -Werror=conflicts-sr

Expand Down
333 changes: 333 additions & 0 deletions src/infer.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,333 @@
/*
* Copyright 2021 The SELint Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "infer.h"
#include "color.h"
#include "maps.h"
#include "util.h"

/* Uses directly in the testsuite */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo, uses -> used

enum selint_error infer_interfaces_shallow(const struct policy_node *node);
enum selint_error infer_interfaces_deep(const struct policy_node *node);

static enum param_flavor name_to_param_flavor(enum name_flavor flavor)
{
switch (flavor) {
case NAME_TYPE:
return PARAM_TYPE;
case NAME_TYPEATTRIBUTE:
return PARAM_TYPEATTRIBUTE;
case NAME_TYPE_OR_ATTRIBUTE:
return PARAM_TYPE_OR_ATTRIBUTE;
case NAME_ROLE:
return PARAM_ROLE;
case NAME_ROLEATTRIBUTE:
return PARAM_ROLEATTRIBUTE;
case NAME_ROLE_OR_ATTRIBUTE:
return PARAM_ROLE_OR_ATTRIBUTE;
case NAME_CLASS:
return PARAM_CLASS;
case NAME_OBJECT_NAME:
return PARAM_OBJECT_NAME;
default:
// should never happen
return PARAM_UNKNOWN;
}
}

enum infer_type { IN_SHALLOW, IN_DEEP };

struct infer_data {
struct interface_trait *if_data;
enum infer_type mode;
const struct policy_node *node;
};

static const char *trait_type_to_str(enum trait_type t)
{
switch (t) {
case INTERFACE_TRAIT:
return "interface";
case TEMPLATE_TRAIT:
return "template";
case MACRO_TRAIT:
return "macro";
default:
// should never happen
return "unknown-trait-type";
}
}

static void infer_func(const char *name, enum name_flavor flavor, unsigned short id, void *visitor_data)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this (and infer_interface) get a comment explaining their usage? It's not clear to me from the names, and it's slow progress trying to infer from the implementation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, reading more, I think I have a rough idea of what these are both for based on how they're used. I still would like to have something with the functions to help orient anyone reading the code.

{
if (!name) {
return;
}

struct infer_data *data = visitor_data;

const char *dollar = strchr(name, '$');
if (!dollar) {
return;
}

if (0 == strcmp(name, "$*") && flavor == NAME_IF_PARAM && id == 1) {
const struct interface_trait *call_trait = look_up_in_if_traits_map(data->node->data.ic_data->name);
if (!call_trait) {
print_if_verbose("No call trait for %s\n", data->node->data.ic_data->name);
} else {
for (int i = 0; i < TRAIT_MAX_PARAMETERS; i++) {
data->if_data->parameters[i] = call_trait->parameters[i];
}
}
return;
}

char *param_end;
errno = 0;
unsigned long param_no = strtoul(dollar + 1, &param_end, 10);
if (param_no == 0 || errno != 0) {
fprintf(stderr, "%sError%s: Failed to parse parameter number from name '%s' in %s %s!\n",
color_error(), color_reset(),
name,
trait_type_to_str(data->if_data->type),
data->if_data->name);
return;
}
param_no--; // start counting at 0 ($0 is invalid)
if (param_no > TRAIT_MAX_PARAMETERS) {
fprintf(stderr, "%sWarning%s: Only up to %u parameters supported, parsed %lu from name '%s' in %s %s!\n",
color_warning(), color_reset(),
TRAIT_MAX_PARAMETERS,
param_no,
name,
trait_type_to_str(data->if_data->type),
data->if_data->name);
return;
}

// skip dash of exclusions
if (name[0] == '-') {
name = name + 1;
}

if (dollar == name && *param_end == '\0') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I may very well be missing something here, but I'm concerned about the situation where a parameter is incorrectly used in a way that would infer as two different parameters. Something like:

allow $1 foo_t:file read;
role $1 types foo_t;

This would be a compile error, but I think the logic here would end up setting the flavor of $1 to PARAM_ROLE, since the second line just overwrites the first? It seems like it would be valuable info to have to know if a parameter was being used inconsistently.

Then again, I could definitely be missing something, so feel free to let me know if this scenario is already handled.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, a subsequent usage of the same no yet final inferred parameter will completely override the current type.
Probably the inferred state shouldn't be a type of an enum but a bit-mask where its possible to store the information a parameter is used as a type-or-attribute, a role and a template string-part. So analysis checks can then create reports like "Parameter X has conflicting usages", "Interface foo uses templated parameter X (declare as template)".

// name is just a parameter, e.g. '$1'
if (data->if_data->parameters[param_no] < PARAM_FINAL_INFERRED) {
if (data->mode == IN_DEEP && flavor == NAME_IF_PARAM) {
const struct interface_trait *call_trait = look_up_in_if_traits_map(data->node->data.ic_data->name);
if (!call_trait) {
print_if_verbose("No call trait for %s\n", data->node->data.ic_data->name);
} else {
data->if_data->parameters[param_no] = call_trait->parameters[id-1];
}
} else {
data->if_data->parameters[param_no] = name_to_param_flavor(flavor);
}
}
return;
}

if (data->if_data->parameters[param_no] < PARAM_FINAL_INFERRED) {
data->if_data->parameters[param_no] = PARAM_TEXT;
}
}

static enum selint_error infer_interface(struct interface_trait *if_trait, const struct policy_node *node, enum infer_type mode)
{
struct infer_data data = { if_trait, mode, NULL };
static unsigned short nesting = 1;

if (nesting > 40) {
return SELINT_IF_CALL_LOOP;
}

for (; node && node->flavor != NODE_INTERFACE_DEF && node->flavor != NODE_TEMP_DEF; node = dfs_next(node)) {
if (mode == IN_DEEP && node->flavor == NODE_IF_CALL) {
const char *call_name = node->data.ic_data->name;
struct interface_trait *call_trait = look_up_in_if_traits_map(call_name);
if (!call_trait) {
print_if_verbose("No call trait found for %s\n", call_name);
} else if (!call_trait->is_inferred && call_trait->type != MACRO_TRAIT) {
nesting++;
enum selint_error ret = infer_interface(call_trait, call_trait->node->first_child, mode);
nesting--;
if (ret != SELINT_SUCCESS) {
return ret;
}
}
}

data.node = node;
visit_names_in_node(node, infer_func, &data);
}

return SELINT_SUCCESS;
}

enum selint_error infer_interfaces_shallow(const struct policy_node *node)
{
for (const struct policy_node *cur_node = node; cur_node; cur_node = cur_node->next) {
// skip non ifs
if (cur_node->flavor != NODE_INTERFACE_DEF && cur_node->flavor != NODE_TEMP_DEF) {
continue;
}

struct interface_trait *if_trait = malloc(sizeof(struct interface_trait));
if_trait->name = strdup(cur_node->data.str);
if_trait->type = (cur_node->flavor == NODE_TEMP_DEF) ? TEMPLATE_TRAIT : INTERFACE_TRAIT;
if_trait->is_inferred = false;
memset(if_trait->parameters, 0, sizeof if_trait->parameters);
if_trait->node = cur_node;

enum selint_error ret = infer_interface(if_trait, cur_node->first_child, IN_SHALLOW);
if (ret != SELINT_SUCCESS) {
return ret;
}

bool is_inferred = true;
for (int i = 0; i < TRAIT_MAX_PARAMETERS; ++i) {
if (if_trait->parameters[i] == PARAM_UNKNOWN) {
is_inferred = false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like a few lines of spaces got in this file (here and line 213 below)

break;
}
}
if_trait->is_inferred = is_inferred;

insert_into_if_traits_map(cur_node->data.str, if_trait);
}

return SELINT_SUCCESS;
}

enum selint_error infer_interfaces_deep(const struct policy_node *node)
{
for (const struct policy_node *cur_node = node; cur_node; cur_node = cur_node->next) {
// skip non ifs
if (cur_node->flavor != NODE_INTERFACE_DEF && cur_node->flavor != NODE_TEMP_DEF) {
continue;
}

const char *if_name = cur_node->data.str;
struct interface_trait *if_trait = look_up_in_if_traits_map(if_name);

if (if_trait->is_inferred) {
continue;
}

enum selint_error ret = infer_interface(if_trait, cur_node->first_child, IN_DEEP);
if (ret != SELINT_SUCCESS) {
return ret;
}
for (int i = 0; i < TRAIT_MAX_PARAMETERS; ++i) {
if (if_trait->parameters[i] == PARAM_UNKNOWN) {
print_if_verbose("Parameter %d of %s %s not inferred\n",
i + 1,
trait_type_to_str(if_trait->type),
if_trait->name);
}
}
if_trait->is_inferred = true;
}

return SELINT_SUCCESS;
}

static void add_refpolicy_macro(const char *name, int param_count, const enum param_flavor flavors[])
{
struct interface_trait *if_trait = malloc(sizeof(struct interface_trait));
if_trait->name = strdup(name);
if_trait->type = MACRO_TRAIT;
if_trait->is_inferred = true;
if_trait->node = NULL;
for (int i = 0; i < TRAIT_MAX_PARAMETERS; i++) {
if (i < param_count) {
if_trait->parameters[i] = flavors[i];
} else {
if_trait->parameters[i] = PARAM_INITIAL;
}
}

insert_into_if_traits_map(name, if_trait);
}

enum selint_error infer_all_interfaces(const struct policy_file_list *files)
{
// manually insert common refpolicy macros, since macro definitions are not
// part of the internal policy representation
add_refpolicy_macro("can_exec",
2,
(enum param_flavor[2]){ PARAM_TYPE_OR_ATTRIBUTE, PARAM_TYPE_OR_ATTRIBUTE });
add_refpolicy_macro("filetrans_pattern",
5,
(enum param_flavor[5]){ PARAM_TYPE_OR_ATTRIBUTE, PARAM_TYPE_OR_ATTRIBUTE, PARAM_TYPE, PARAM_CLASS, PARAM_OBJECT_NAME });
add_refpolicy_macro("filetrans_add_pattern",
5,
(enum param_flavor[5]){ PARAM_TYPE_OR_ATTRIBUTE, PARAM_TYPE_OR_ATTRIBUTE, PARAM_TYPE, PARAM_CLASS, PARAM_OBJECT_NAME });
add_refpolicy_macro("domtrans_pattern",
3,
(enum param_flavor[3]){ PARAM_TYPE_OR_ATTRIBUTE, PARAM_TYPE_OR_ATTRIBUTE, PARAM_TYPE });
add_refpolicy_macro("domain_auto_transition_pattern",
3,
(enum param_flavor[3]){ PARAM_TYPE_OR_ATTRIBUTE, PARAM_TYPE_OR_ATTRIBUTE, PARAM_TYPE });
add_refpolicy_macro("admin_pattern",
2,
(enum param_flavor[2]){ PARAM_TYPE_OR_ATTRIBUTE, PARAM_TYPE_OR_ATTRIBUTE });
add_refpolicy_macro("stream_connect_pattern",
4,
(enum param_flavor[4]){ PARAM_TYPE_OR_ATTRIBUTE, PARAM_TYPE_OR_ATTRIBUTE, PARAM_TYPE_OR_ATTRIBUTE, PARAM_TYPE_OR_ATTRIBUTE });
add_refpolicy_macro("dgram_send_pattern",
4,
(enum param_flavor[4]){ PARAM_TYPE_OR_ATTRIBUTE, PARAM_TYPE_OR_ATTRIBUTE, PARAM_TYPE_OR_ATTRIBUTE, PARAM_TYPE_OR_ATTRIBUTE });


// first infer only simple ifs; do not infer based on other called sub ifs
print_if_verbose("Start shallow infer step...\n");
for (const struct policy_file_node *cur_file = files->head; cur_file; cur_file = cur_file->next) {
enum selint_error ret = infer_interfaces_shallow(cur_file->file->ast);
if (ret != SELINT_SUCCESS) {
return ret;
}
}

// on the second run the policy_nodes are linked to the traits, so we can infer deep
print_if_verbose("Start deep infer step...\n");
for (const struct policy_file_node *cur_file = files->head; cur_file; cur_file = cur_file->next) {
enum selint_error ret = infer_interfaces_deep(cur_file->file->ast);
if (ret != SELINT_SUCCESS) {
return ret;
}
}

print_if_verbose("Finished infer steps\n");

return SELINT_SUCCESS;
}

void free_interface_trait(struct interface_trait *to_free)
{
if (to_free == NULL) {
return;
}

free(to_free->name);
free(to_free);
}
Loading