/* $NetBSD: filter.c,v 1.5 2002/08/28 03:52:44 itojun Exp $ */ /* $OpenBSD: filter.c,v 1.16 2002/08/08 21:18:20 provos Exp $ */ /* * Copyright 2002 Niels Provos * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Niels Provos. * 4. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include __RCSID("$NetBSD: filter.c,v 1.5 2002/08/28 03:52:44 itojun Exp $"); #include #include #include #include #include #include #include #include #include #include #include #include #include "intercept.h" #include "systrace.h" #include "filter.h" extern int allow; extern int noalias; extern int connected; extern char cwd[]; static void logic_free(struct logic *); static int filter_match(struct intercept_tlq *, struct logic *); static void filter_review(struct filterq *); static void filter_policyrecord(struct policy *, struct filter *, const char *, const char *, char *); static void filter_replace(char *, size_t, char *, char *); static int filter_match(struct intercept_tlq *tls, struct logic *logic) { struct intercept_translate *tl; int off = 0; switch (logic->op) { case LOGIC_NOT: return (!filter_match(tls, logic->left)); case LOGIC_OR: if (filter_match(tls, logic->left)) return (1); return (filter_match(tls, logic->right)); case LOGIC_AND: if (!filter_match(tls, logic->left)) return (0); return (filter_match(tls, logic->right)); default: break; } /* Now we just have a logic single */ if (logic->type == NULL) goto match; TAILQ_FOREACH(tl, tls, next) { if (!tl->trans_valid) return (0); if (strcasecmp(tl->name, logic->type)) continue; if (logic->typeoff == -1 || logic->typeoff == off) break; off++; } if (tl == NULL) return (0); match: return (logic->filter_match(tl, logic)); } short filter_evaluate(struct intercept_tlq *tls, struct filterq *fls, int *pflags) { struct filter *filter, *last = NULL; short action, laction = 0; TAILQ_FOREACH(filter, fls, next) { action = filter->match_action; if (filter_match(tls, filter->logicroot)) { /* Profile feedback optimization */ filter->match_count++; if (last != NULL && last->match_action == action && filter->match_count > last->match_count) { TAILQ_REMOVE(fls, last, next); TAILQ_INSERT_AFTER(fls, filter, last, next); } if (action == ICPOLICY_NEVER) action = filter->match_error; *pflags = filter->match_flags; return (action); } /* Keep track of last processed filtered in a group */ last = filter; laction = action; } return (ICPOLICY_ASK); } static void logic_free(struct logic *logic) { if (logic->left) logic_free(logic->left); if (logic->right) logic_free(logic->right); if (logic->type) free(logic->type); if (logic->filterdata) free(logic->filterdata); free(logic); } void filter_free(struct filter *filter) { if (filter->logicroot) logic_free(filter->logicroot); if (filter->rule) free(filter->rule); free(filter); } static void filter_review(struct filterq *fls) { struct filter *filter; int i = 0; printf("Filter review:\n"); TAILQ_FOREACH(filter, fls, next) { i++; printf("%d. %s\n", i, filter->rule); } } static void filter_policyrecord(struct policy *policy, struct filter *filter, const char *emulation, const char *name, char *rule) { /* Record the filter in the policy */ if (filter == NULL) { filter = calloc(1, sizeof(struct filter)); if (filter == NULL) err(1, "%s:%d: calloc", __func__, __LINE__); if ((filter->rule = strdup(rule)) == NULL) err(1, "%s:%d: strdup", __func__, __LINE__); } strlcpy(filter->name, name, sizeof(filter->name)); strlcpy(filter->emulation, emulation, sizeof(filter->emulation)); TAILQ_INSERT_TAIL(&policy->filters, filter, policy_next); policy->nfilters++; policy->flags |= POLICY_CHANGED; } int filter_parse(char *line, struct filter **pfilter) { char *rule; if (parse_filter(line, pfilter) == -1) return (-1); if ((rule = strdup(line)) == NULL) err(1, "%s:%d: strdup", __func__, __LINE__); (*pfilter)->rule = rule; return (0); } /* Translate a simple action like "permit" or "deny[einval]" to numbers */ int filter_parse_simple(char *rule, short *paction, short *pfuture) { char buf[_POSIX2_LINE_MAX]; int isfuture = 1; char *line, *p; if (strlcpy(buf, rule, sizeof(buf)) >= sizeof(buf)) return (-1); line = buf; if (!strcmp("permit", line)) { *paction = *pfuture = ICPOLICY_PERMIT; return (0); } else if (!strcmp("permit-now", line)) { *paction = ICPOLICY_PERMIT; return (0); } else if (strncmp("deny", line, 4)) return (-1); line +=4 ; if (!strncmp("-now", line, 4)) { line += 4; isfuture = 0; } *paction = ICPOLICY_NEVER; switch (line[0]) { case '\0': break; case '[': line++; p = strsep(&line, "]"); if (line == NULL || *line != '\0') return (-1); *paction = systrace_error_translate(p); if (*paction == -1) return (-1); break; default: return (-1); } if (isfuture) *pfuture = *paction; return (NULL); } void filter_modifypolicy(int fd, int policynr, const char *emulation, const char *name, short future) { struct systrace_revalias *reverse = NULL; if (!noalias) reverse = systrace_find_reverse(emulation, name); if (reverse == NULL) { if (systrace_modifypolicy(fd, policynr, name, future) == -1) errx(1, "%s:%d: modify policy for %s-%s", __func__, __LINE__, emulation, name); } else { struct systrace_alias *alias; /* For every system call associated with this alias * set the permanent in-kernel policy. */ TAILQ_FOREACH(alias, &reverse->revl, next) { if(systrace_modifypolicy(fd, policynr, alias->name, future) == -1) errx(1, "%s:%d: modify policy for %s-%s", __func__, __LINE__, emulation, alias->name); } } } int filter_prepolicy(int fd, struct policy *policy) { int res; struct filter *filter, *parsed; struct filterq *fls; short action, future; /* Commit all matching pre-filters */ for (filter = TAILQ_FIRST(&policy->prefilters); filter; filter = TAILQ_FIRST(&policy->prefilters)) { future = ICPOLICY_ASK; TAILQ_REMOVE(&policy->prefilters, filter, policy_next); res = 0; parsed = NULL; /* Special rules that are not real filters */ if (filter_parse_simple(filter->rule, &action, &future) == -1) res = filter_parse(filter->rule, &parsed); if (res == -1) errx(1, "%s:%d: can not parse \"%s\"", __func__, __LINE__, filter->rule); if (future == ICPOLICY_ASK) { fls = systrace_policyflq(policy, policy->emulation, filter->name); TAILQ_INSERT_TAIL(fls, parsed, next); } else { filter_modifypolicy(fd, policy->policynr, policy->emulation, filter->name, future); } filter_policyrecord(policy, parsed, policy->emulation, filter->name, filter->rule); filter_free(filter); } /* Existing policy applied undo changed flag */ policy->flags &= ~POLICY_CHANGED; return (0); } short filter_ask(struct intercept_tlq *tls, struct filterq *fls, int policynr, const char *emulation, const char *name, char *output, short *pfuture, int *pflags) { char line[2*MAXPATHLEN], *p; struct filter *filter; struct policy *policy; short action; int first = 1; *pfuture = ICPOLICY_ASK; *pflags = 0; if ((policy = systrace_findpolnr(policynr)) == NULL) errx(1, "%s:%d: no policy %d", __func__, __LINE__, policynr); if (!allow) printf("%s\n", output); else { /* Automatically allow */ if (tls != NULL) { struct intercept_translate *tl; char compose[2*MAXPATHLEN], *l; char *lst = NULL; int set = 0; /* Explicitly match every component */ line[0] = '\0'; TAILQ_FOREACH(tl, tls, next) { if (!tl->trans_valid) break; l = intercept_translate_print(tl); if (l == NULL) continue; snprintf(compose, sizeof(compose), "%s%s eq \"%s\"", tl->name, lst && !strcmp(tl->name, lst) ? "[1]" : "", l); lst = tl->name; if (set) strlcat(line, " and ", sizeof(line)); else set = 1; strlcat(line, compose, sizeof(line)); } if (!set) strlcpy(line, "true", sizeof(line)); strlcat(line, " then permit", sizeof(line)); } else strlcpy(line, "permit", sizeof(line)); } while (1) { filter = NULL; if (!allow) { /* Ask for a policy */ if (!connected) printf("Answer: "); else { /* Do not prompt the first time */ if (!first) { printf("WRONG\n"); } } fgets(line, sizeof(line), stdin); p = line; strsep(&p, "\n"); } else if (!first) { /* Error with filter */ errx(1, "Filter generation error: %s", line); } first = 0; /* Simple keywords */ if (!strcasecmp(line, "detach")) { if (policy->nfilters) { policy->flags |= POLICY_UNSUPERVISED; action = ICPOLICY_NEVER; } else { policy->flags |= POLICY_DETACHED; policy->flags |= POLICY_CHANGED; action = ICPOLICY_PERMIT; } goto out; } else if (!strcasecmp(line, "kill")) { action = ICPOLICY_KILL; goto out; } else if (!strcasecmp(line, "review") && fls != NULL) { filter_review(fls); continue; } if (filter_parse_simple(line, &action, pfuture) != -1) { if (*pfuture == ICPOLICY_ASK) goto out; break; } if (fls == NULL) { printf("Syntax error.\n"); continue; } if (filter_parse(line, &filter) == -1) continue; TAILQ_INSERT_TAIL(fls, filter, next); action = filter_evaluate(tls, fls, pflags); if (action == ICPOLICY_ASK) { TAILQ_REMOVE(fls, filter, next); printf("Filter unmatched. Freeing it\n"); filter_free(filter); continue; } break; } filter_policyrecord(policy, filter, emulation, name, line); out: if (connected) printf("OKAY\n"); return (action); } static void filter_replace(char *buf, size_t buflen, char *match, char *repl) { while (strrpl(buf, buflen, match, repl) != NULL) ; } char * filter_expand(char *data) { static char expand[2*MAXPATHLEN]; char *what; if (data != NULL) strlcpy(expand, data, sizeof(expand)); what = getenv("HOME"); if (what != NULL) filter_replace(expand, sizeof(expand), "$HOME", what); what = getenv("USER"); if (what != NULL) filter_replace(expand, sizeof(expand), "$USER", what); filter_replace(expand, sizeof(expand), "$CWD", cwd); return (expand); } int filter_fnmatch(struct intercept_translate *tl, struct logic *logic) { int res; char *line; if (tl->trans_size == 0) return (0); if ((line = intercept_translate_print(tl)) == NULL) return (0); #ifdef __NetBSD__ res = fnmatch(logic->filterdata, line, FNM_PATHNAME); #else res = fnmatch(logic->filterdata, line, FNM_PATHNAME | FNM_LEADING_DIR); #endif return (res == 0); } int filter_substrmatch(struct intercept_translate *tl, struct logic *logic) { char *line; if ((line = intercept_translate_print(tl)) == NULL) return (0); return (strstr(line, logic->filterdata) != NULL); } int filter_negsubstrmatch(struct intercept_translate *tl, struct logic *logic) { char *line; if ((line = intercept_translate_print(tl)) == NULL) return (0); return (strstr(line, logic->filterdata) == NULL); } int filter_stringmatch(struct intercept_translate *tl, struct logic *logic) { char *line; if ((line = intercept_translate_print(tl)) == NULL) return (0); return (!strcasecmp(line, logic->filterdata)); } int filter_negstringmatch(struct intercept_translate *tl, struct logic *logic) { char *line; if ((line = intercept_translate_print(tl)) == NULL) return (1); return (strcasecmp(line, logic->filterdata) != 0); } int filter_inpath(struct intercept_translate *tl, struct logic *logic) { char *line, c; int len; if ((line = intercept_translate_print(tl)) == NULL) return (0); len = strlen(line); if (len == 0 || len > strlen(logic->filterdata)) return (0); /* Root is always in path */ if (len == 1) return (line[0] == '/'); /* Complete filename needs to fit */ if (strncmp(line, logic->filterdata, len)) return (0); /* Termination has to be \0 or / */ c = ((char *)logic->filterdata)[len]; if (c != '/' && c != '\0') return (0); return (1); } int filter_true(struct intercept_translate *tl, struct logic *logic) { return (1); }