diff --git a/sys/kern/kern_racct.c b/sys/kern/kern_racct.c --- a/sys/kern/kern_racct.c +++ b/sys/kern/kern_racct.c @@ -623,20 +623,29 @@ /* * Increase allocation of 'resource' by 'amount' for credential 'cred'. - * Doesn't check for limits and never fails. + * Return 0 if it's below limits, or errno, if it's not. */ -void +int racct_add_cred(struct ucred *cred, int resource, uint64_t amount) { + int error; if (!racct_enable) - return; + return (0); SDT_PROBE3(racct, , rusage, add__cred, cred, resource, amount); RACCT_LOCK(); +#ifdef RCTL + error = rctl_enforce_cred(cred, resource, amount); + if (error && RACCT_IS_DENIABLE(resource)) { + SDT_PROBE3(racct, , rusage, add__failure, cred, resource, amount); + return (error); + } +#endif racct_add_cred_locked(cred, resource, amount); RACCT_UNLOCK(); + return (0); } /* diff --git a/sys/kern/kern_rctl.c b/sys/kern/kern_rctl.c --- a/sys/kern/kern_rctl.c +++ b/sys/kern/kern_rctl.c @@ -221,6 +221,8 @@ static int rctl_rule_fully_specified(const struct rctl_rule *rule); static void rctl_rule_to_sbuf(struct sbuf *sb, const struct rctl_rule *rule); +static int rctl_enforce_racct(struct racct *racct, int resource, uint64_t amount, + struct ucred *cred); static MALLOC_DEFINE(M_RCTL, "rctl", "Resource Limits"); @@ -332,16 +334,13 @@ } static struct racct * -rctl_proc_rule_to_racct(const struct proc *p, const struct rctl_rule *rule) +rctl_proc_rule_to_racct_cred(const struct ucred *cred, + const struct rctl_rule *rule) { - struct ucred *cred = p->p_ucred; - - ASSERT_RACCT_ENABLED(); - RACCT_LOCK_ASSERT(); + KASSERT(rule->rr_per != RCTL_SUBJECT_TYPE_PROCESS, + ("rctl_proc_rule_to_racct_cred: cannot get process racct")); switch (rule->rr_per) { - case RCTL_SUBJECT_TYPE_PROCESS: - return (p->p_racct); case RCTL_SUBJECT_TYPE_USER: return (cred->cr_ruidinfo->ui_racct); case RCTL_SUBJECT_TYPE_LOGINCLASS: @@ -353,6 +352,22 @@ } } +static struct racct * +rctl_proc_rule_to_racct(const struct proc *p, const struct rctl_rule *rule) +{ + struct ucred *cred = p->p_ucred; + + ASSERT_RACCT_ENABLED(); + RACCT_LOCK_ASSERT(); + + switch (rule->rr_per) { + case RCTL_SUBJECT_TYPE_PROCESS: + return (p->p_racct); + default: + return (rctl_proc_rule_to_racct_cred(cred, rule)); + } +} + /* * Return the amount of resource that can be allocated by 'p' before * hitting 'rule'. @@ -372,6 +387,26 @@ return (available); } +/* + * Return the amount of resource that can be allocated by 'cred' before + * hitting 'rule'. + */ +static int64_t +rctl_available_resource_cred(const struct ucred *cred, + const struct rctl_rule *rule) +{ + const struct racct *racct; + int64_t available; + + ASSERT_RACCT_ENABLED(); + RACCT_LOCK_ASSERT(); + + racct = rctl_proc_rule_to_racct_cred(cred, rule); + available = rule->rr_amount - racct->r_resources[rule->rr_resource]; + + return (available); +} + /* * Called every second for proc, uidinfo, loginclass, and jail containers. * If the limit isn't exceeded, it decreases the usage amount to zero. @@ -489,6 +524,130 @@ return (a * b); } +/* + * Check whether the credential 'cred' can allocate 'amount' of 'resource' in + * addition to what it keeps allocated now. Returns non-zero if the allocation + * should be denied, 0 otherwise. Does not enforce rules whose actions require + * a process, i.e., throttle and sig*. + */ +int +rctl_enforce_cred(struct ucred *cred, int resource, uint64_t amount) +{ + int error = 0; + error |= rctl_enforce_racct(cred->cr_ruidinfo->ui_racct, + resource, amount, cred); + error |= rctl_enforce_racct(cred->cr_loginclass->lc_racct, + resource, amount, cred); + error |= rctl_enforce_racct(cred->cr_prison->pr_prison_racct->prr_racct, + resource, amount, cred); + return (error); +} + +static int +rctl_enforce_racct(struct racct *racct, int resource, uint64_t amount, struct ucred *cred) { + static struct timeval log_lasttime, devctl_lasttime; + static int log_curtime = 0, devctl_curtime = 0; + struct rctl_rule *rule; + struct rctl_rule_link *link; + struct sbuf sb; + char *buf; + int64_t available; + int should_deny = 0; + + ASSERT_RACCT_ENABLED(); + RACCT_LOCK_ASSERT(); + + /* + * There may be more than one matching rule; go through all of them. + * Denial should be done last, after logging and sending signals. + */ + LIST_FOREACH(link, &racct->r_rule_links, rrl_next) { + rule = link->rrl_rule; + if (rule->rr_resource != resource) + continue; + if (rule->rr_per == RCTL_SUBJECT_TYPE_PROCESS) + continue; + + available = rctl_available_resource_cred(cred, rule); + if (available >= (int64_t)amount) { + link->rrl_exceeded = 0; + continue; + } + + switch (rule->rr_action) { + case RCTL_ACTION_DENY: + should_deny = 1; + continue; + case RCTL_ACTION_LOG: + /* + * If rrl_exceeded != 0, it means we've already + * logged a warning for this process. + */ + if (link->rrl_exceeded != 0) + continue; + + if (!ppsratecheck(&log_lasttime, &log_curtime, + rctl_log_rate_limit)) + continue; + + buf = malloc(RCTL_LOG_BUFSIZE, M_RCTL, M_NOWAIT); + if (buf == NULL) { + printf("rctl_enforce_racct: out of memory\n"); + continue; + } + sbuf_new(&sb, buf, RCTL_LOG_BUFSIZE, SBUF_FIXEDLEN); + rctl_rule_to_sbuf(&sb, rule); + sbuf_finish(&sb); + printf("rctl: rule \"%s\" matched by uid %d, jail %s\n", + sbuf_data(&sb), cred->cr_uid, + cred->cr_prison->pr_prison_racct->prr_name); + sbuf_delete(&sb); + free(buf, M_RCTL); + link->rrl_exceeded = 1; + continue; + case RCTL_ACTION_DEVCTL: + if (link->rrl_exceeded != 0) + continue; + + if (!ppsratecheck(&devctl_lasttime, &devctl_curtime, + rctl_devctl_rate_limit)) + continue; + + buf = malloc(RCTL_LOG_BUFSIZE, M_RCTL, M_NOWAIT); + if (buf == NULL) { + printf("rctl_enforce_racct: out of memory\n"); + continue; + } + sbuf_new(&sb, buf, RCTL_LOG_BUFSIZE, SBUF_FIXEDLEN); + sbuf_printf(&sb, "rule="); + rctl_rule_to_sbuf(&sb, rule); + sbuf_printf(&sb, " ruid=%d jail=%s", cred->cr_ruid, + cred->cr_prison->pr_prison_racct->prr_name); + sbuf_finish(&sb); + devctl_notify("RCTL", "rule", "matched", + sbuf_data(&sb)); + sbuf_delete(&sb); + free(buf, M_RCTL); + link->rrl_exceeded = 1; + continue; + case RCTL_ACTION_THROTTLE: + continue; + default: + continue; + } + } + + if (should_deny) { + /* + * Return fake error code; the caller should change it + * into one proper for the situation - EFSIZ, ENOMEM etc. + */ + return (EDOOFUS); + } + + return (0); +} + /* * Check whether the proc 'p' can allocate 'amount' of 'resource' in addition * to what it keeps allocated now. Returns non-zero if the allocation should diff --git a/sys/sys/racct.h b/sys/sys/racct.h --- a/sys/sys/racct.h +++ b/sys/sys/racct.h @@ -175,7 +175,7 @@ } while (0) int racct_add(struct proc *p, int resource, uint64_t amount); -void racct_add_cred(struct ucred *cred, int resource, uint64_t amount); +int racct_add_cred(struct ucred *cred, int resource, uint64_t amount); void racct_add_force(struct proc *p, int resource, uint64_t amount); void racct_add_buf(struct proc *p, const struct buf *bufp, int is_write); int racct_set(struct proc *p, int resource, uint64_t amount); diff --git a/sys/sys/rctl.h b/sys/sys/rctl.h --- a/sys/sys/rctl.h +++ b/sys/sys/rctl.h @@ -142,10 +142,12 @@ int rctl_rule_add(struct rctl_rule *rule); int rctl_rule_remove(struct rctl_rule *filter); int rctl_enforce(struct proc *p, int resource, uint64_t amount); +int rctl_enforce_cred(struct ucred *cred, int resource, uint64_t amount); void rctl_throttle_decay(struct racct *racct, int resource); int64_t rctl_pcpu_available(const struct proc *p); uint64_t rctl_get_limit(struct proc *p, int resource); uint64_t rctl_get_available(struct proc *p, int resource); +uint64_t rctl_get_available_cred(struct ucred *cred, int resource); const char *rctl_resource_name(int resource); void rctl_proc_ucred_changed(struct proc *p, struct ucred *newcred); int rctl_proc_fork(struct proc *parent, struct proc *child);