// SPDX-License-Identifier: GPL-2.0-or-later /* auditsc.c -- System-call auditing support * Handles all system-call specific auditing features. * * Copyright 2003-2004 Red Hat Inc., Durham, North Carolina. * Copyright 2005 Hewlett-Packard Development Company, L.P. * Copyright (C) 2005, 2006 IBM Corporation * All Rights Reserved. * * Written by Rickard E. (Rik) Faith <faith@redhat.com> * * Many of the ideas implemented here are from Stephen C. Tweedie, * especially the idea of avoiding a copy by using getname. * * The method for actual interception of syscall entry and exit (not in * this file -- see entry.S) is based on a GPL'd patch written by * okir@suse.de and Copyright 2003 SuSE Linux AG. * * POSIX message queue support added by George Wilson <ltcgcw@us.ibm.com>, * 2006. * * The support of additional filter rules compares (>, <, >=, <=) was * added by Dustin Kirkland <dustin.kirkland@us.ibm.com>, 2005. * * Modified by Amy Griffis <amy.griffis@hp.com> to collect additional * filesystem information. * * Subject and object context labeling support added by <danjones@us.ibm.com> * and <dustin.kirkland@us.ibm.com> for LSPP certification compliance.
*/
/* flags stating the success for a syscall */ #define AUDITSC_INVALID 0 #define AUDITSC_SUCCESS 1 #define AUDITSC_FAILURE 2
/* no execve audit message should be longer than this (userspace limits),
* see the note near the top of audit_log_execve_info() about this value */ #define MAX_EXECVE_AUDIT_LEN 7500
/* max length to print of cmdline/proctitle value during audit */ #define MAX_PROCTITLE_AUDIT_LEN 128
/* number of audit rules */ int audit_n_rules;
/* determines whether we collect data for signals sent */ int audit_signals;
struct audit_aux_data { struct audit_aux_data *next; int type;
};
/* Number of target pids per aux struct. */ #define AUDIT_AUX_PIDS 16
/* * We keep a linked list of fixed-sized (31 pointer) arrays of audit_chunk *; * ->first_trees points to its beginning, ->trees - to the current end of data. * ->tree_count is the number of free entries in array pointed to by ->trees. * Original condition is (NULL, NULL, 0); as soon as it grows we never revert to NULL, * "empty" becomes (p, p, 31) afterwards. We don't shrink the list (and seriously, * it's going to remain 1-element for almost any setup) until we free context itself. * References in it _are_ dropped - at the same time we free/drop aux stuff.
*/
staticvoid unroll_tree_refs(struct audit_context *ctx, struct audit_tree_refs *p, int count)
{ struct audit_tree_refs *q; int n;
if (!p) { /* we started with empty chain */
p = ctx->first_trees;
count = 31; /* if the very first allocation has failed, nothing to do */ if (!p) return;
}
n = count; for (q = p; q != ctx->trees; q = q->next, n = 31) { while (n--) {
audit_put_chunk(q->c[n]);
q->c[n] = NULL;
}
} while (n-- > ctx->tree_count) {
audit_put_chunk(q->c[n]);
q->c[n] = NULL;
}
ctx->trees = p;
ctx->tree_count = count;
}
if (!tree) return 0; /* full ones */ for (p = ctx->first_trees; p != ctx->trees; p = p->next) { for (n = 0; n < 31; n++) if (audit_tree_match(p->c[n], tree)) return 1;
} /* partial */ if (p) { for (n = ctx->tree_count; n < 31; n++) if (audit_tree_match(p->c[n], tree)) return 1;
} return 0;
}
staticint audit_field_compare(struct task_struct *tsk, conststruct cred *cred, struct audit_field *f, struct audit_context *ctx, struct audit_names *name)
{ switch (f->val) { /* process to file object comparisons */ case AUDIT_COMPARE_UID_TO_OBJ_UID: return audit_compare_uid(cred->uid, name, f, ctx); case AUDIT_COMPARE_GID_TO_OBJ_GID: return audit_compare_gid(cred->gid, name, f, ctx); case AUDIT_COMPARE_EUID_TO_OBJ_UID: return audit_compare_uid(cred->euid, name, f, ctx); case AUDIT_COMPARE_EGID_TO_OBJ_GID: return audit_compare_gid(cred->egid, name, f, ctx); case AUDIT_COMPARE_AUID_TO_OBJ_UID: return audit_compare_uid(audit_get_loginuid(tsk), name, f, ctx); case AUDIT_COMPARE_SUID_TO_OBJ_UID: return audit_compare_uid(cred->suid, name, f, ctx); case AUDIT_COMPARE_SGID_TO_OBJ_GID: return audit_compare_gid(cred->sgid, name, f, ctx); case AUDIT_COMPARE_FSUID_TO_OBJ_UID: return audit_compare_uid(cred->fsuid, name, f, ctx); case AUDIT_COMPARE_FSGID_TO_OBJ_GID: return audit_compare_gid(cred->fsgid, name, f, ctx); /* uid comparisons */ case AUDIT_COMPARE_UID_TO_AUID: return audit_uid_comparator(cred->uid, f->op,
audit_get_loginuid(tsk)); case AUDIT_COMPARE_UID_TO_EUID: return audit_uid_comparator(cred->uid, f->op, cred->euid); case AUDIT_COMPARE_UID_TO_SUID: return audit_uid_comparator(cred->uid, f->op, cred->suid); case AUDIT_COMPARE_UID_TO_FSUID: return audit_uid_comparator(cred->uid, f->op, cred->fsuid); /* auid comparisons */ case AUDIT_COMPARE_AUID_TO_EUID: return audit_uid_comparator(audit_get_loginuid(tsk), f->op,
cred->euid); case AUDIT_COMPARE_AUID_TO_SUID: return audit_uid_comparator(audit_get_loginuid(tsk), f->op,
cred->suid); case AUDIT_COMPARE_AUID_TO_FSUID: return audit_uid_comparator(audit_get_loginuid(tsk), f->op,
cred->fsuid); /* euid comparisons */ case AUDIT_COMPARE_EUID_TO_SUID: return audit_uid_comparator(cred->euid, f->op, cred->suid); case AUDIT_COMPARE_EUID_TO_FSUID: return audit_uid_comparator(cred->euid, f->op, cred->fsuid); /* suid comparisons */ case AUDIT_COMPARE_SUID_TO_FSUID: return audit_uid_comparator(cred->suid, f->op, cred->fsuid); /* gid comparisons */ case AUDIT_COMPARE_GID_TO_EGID: return audit_gid_comparator(cred->gid, f->op, cred->egid); case AUDIT_COMPARE_GID_TO_SGID: return audit_gid_comparator(cred->gid, f->op, cred->sgid); case AUDIT_COMPARE_GID_TO_FSGID: return audit_gid_comparator(cred->gid, f->op, cred->fsgid); /* egid comparisons */ case AUDIT_COMPARE_EGID_TO_SGID: return audit_gid_comparator(cred->egid, f->op, cred->sgid); case AUDIT_COMPARE_EGID_TO_FSGID: return audit_gid_comparator(cred->egid, f->op, cred->fsgid); /* sgid comparison */ case AUDIT_COMPARE_SGID_TO_FSGID: return audit_gid_comparator(cred->sgid, f->op, cred->fsgid); default:
WARN(1, "Missing AUDIT_COMPARE define. Report as a bug\n"); return 0;
} return 0;
}
/* Determine if any context name data matches a rule's watch data */ /* Compare a task_struct with an audit_rule. Return 1 on match, 0 * otherwise. * * If task_creation is true, this is an explicit indication that we are * filtering a task rule at task creation time. This and tsk == current are * the only situations where tsk->cred may be accessed without an rcu read lock.
*/ staticint audit_filter_rules(struct task_struct *tsk, struct audit_krule *rule, struct audit_context *ctx, struct audit_names *name, enum audit_state *state, bool task_creation)
{ conststruct cred *cred; int i, need_sid = 1; struct lsm_prop prop = { }; unsignedint sessionid;
if (ctx && rule->prio <= ctx->prio) return 0;
cred = rcu_dereference_check(tsk->cred, tsk == current || task_creation);
for (i = 0; i < rule->field_count; i++) { struct audit_field *f = &rule->fields[i]; struct audit_names *n; int result = 0;
pid_t pid;
switch (f->type) { case AUDIT_PID:
pid = task_tgid_nr(tsk);
result = audit_comparator(pid, f->op, f->val); break; case AUDIT_PPID: if (ctx) { if (!ctx->ppid)
ctx->ppid = task_ppid_nr(tsk);
result = audit_comparator(ctx->ppid, f->op, f->val);
} break; case AUDIT_EXE:
result = audit_exe_compare(tsk, rule->exe); if (f->op == Audit_not_equal)
result = !result; break; case AUDIT_UID:
result = audit_uid_comparator(cred->uid, f->op, f->uid); break; case AUDIT_EUID:
result = audit_uid_comparator(cred->euid, f->op, f->uid); break; case AUDIT_SUID:
result = audit_uid_comparator(cred->suid, f->op, f->uid); break; case AUDIT_FSUID:
result = audit_uid_comparator(cred->fsuid, f->op, f->uid); break; case AUDIT_GID:
result = audit_gid_comparator(cred->gid, f->op, f->gid); if (f->op == Audit_equal) { if (!result)
result = groups_search(cred->group_info, f->gid);
} elseif (f->op == Audit_not_equal) { if (result)
result = !groups_search(cred->group_info, f->gid);
} break; case AUDIT_EGID:
result = audit_gid_comparator(cred->egid, f->op, f->gid); if (f->op == Audit_equal) { if (!result)
result = groups_search(cred->group_info, f->gid);
} elseif (f->op == Audit_not_equal) { if (result)
result = !groups_search(cred->group_info, f->gid);
} break; case AUDIT_SGID:
result = audit_gid_comparator(cred->sgid, f->op, f->gid); break; case AUDIT_FSGID:
result = audit_gid_comparator(cred->fsgid, f->op, f->gid); break; case AUDIT_SESSIONID:
sessionid = audit_get_sessionid(tsk);
result = audit_comparator(sessionid, f->op, f->val); break; case AUDIT_PERS:
result = audit_comparator(tsk->personality, f->op, f->val); break; case AUDIT_ARCH: if (ctx)
result = audit_comparator(ctx->arch, f->op, f->val); break;
case AUDIT_EXIT: if (ctx && ctx->return_valid != AUDITSC_INVALID)
result = audit_comparator(ctx->return_code, f->op, f->val); break; case AUDIT_SUCCESS: if (ctx && ctx->return_valid != AUDITSC_INVALID) { if (f->val)
result = audit_comparator(ctx->return_valid, f->op, AUDITSC_SUCCESS); else
result = audit_comparator(ctx->return_valid, f->op, AUDITSC_FAILURE);
} break; case AUDIT_DEVMAJOR: if (name) { if (audit_comparator(MAJOR(name->dev), f->op, f->val) ||
audit_comparator(MAJOR(name->rdev), f->op, f->val))
++result;
} elseif (ctx) {
list_for_each_entry(n, &ctx->names_list, list) { if (audit_comparator(MAJOR(n->dev), f->op, f->val) ||
audit_comparator(MAJOR(n->rdev), f->op, f->val)) {
++result; break;
}
}
} break; case AUDIT_DEVMINOR: if (name) { if (audit_comparator(MINOR(name->dev), f->op, f->val) ||
audit_comparator(MINOR(name->rdev), f->op, f->val))
++result;
} elseif (ctx) {
list_for_each_entry(n, &ctx->names_list, list) { if (audit_comparator(MINOR(n->dev), f->op, f->val) ||
audit_comparator(MINOR(n->rdev), f->op, f->val)) {
++result; break;
}
}
} break; case AUDIT_INODE: if (name)
result = audit_comparator(name->ino, f->op, f->val); elseif (ctx) {
list_for_each_entry(n, &ctx->names_list, list) { if (audit_comparator(n->ino, f->op, f->val)) {
++result; break;
}
}
} break; case AUDIT_OBJ_UID: if (name) {
result = audit_uid_comparator(name->uid, f->op, f->uid);
} elseif (ctx) {
list_for_each_entry(n, &ctx->names_list, list) { if (audit_uid_comparator(n->uid, f->op, f->uid)) {
++result; break;
}
}
} break; case AUDIT_OBJ_GID: if (name) {
result = audit_gid_comparator(name->gid, f->op, f->gid);
} elseif (ctx) {
list_for_each_entry(n, &ctx->names_list, list) { if (audit_gid_comparator(n->gid, f->op, f->gid)) {
++result; break;
}
}
} break; case AUDIT_WATCH: if (name) {
result = audit_watch_compare(rule->watch,
name->ino,
name->dev); if (f->op == Audit_not_equal)
result = !result;
} break; case AUDIT_DIR: if (ctx) {
result = match_tree_refs(ctx, rule->tree); if (f->op == Audit_not_equal)
result = !result;
} break; case AUDIT_LOGINUID:
result = audit_uid_comparator(audit_get_loginuid(tsk),
f->op, f->uid); break; case AUDIT_LOGINUID_SET:
result = audit_comparator(audit_loginuid_set(tsk), f->op, f->val); break; case AUDIT_SADDR_FAM: if (ctx && ctx->sockaddr)
result = audit_comparator(ctx->sockaddr->ss_family,
f->op, f->val); break; case AUDIT_SUBJ_USER: case AUDIT_SUBJ_ROLE: case AUDIT_SUBJ_TYPE: case AUDIT_SUBJ_SEN: case AUDIT_SUBJ_CLR: /* NOTE: this may return negative values indicating a temporary error. We simply treat this as a match for now to avoid losing information that may be wanted. An error message will also be
logged upon error */ if (f->lsm_rule) { if (need_sid) { /* @tsk should always be equal to * @current with the exception of * fork()/copy_process() in which case * the new @tsk creds are still a dup * of @current's creds so we can still * use * security_current_getlsmprop_subj() * here even though it always refs * @current's creds
*/
security_current_getlsmprop_subj(&prop);
need_sid = 0;
}
result = security_audit_rule_match(&prop,
f->type,
f->op,
f->lsm_rule);
} break; case AUDIT_OBJ_USER: case AUDIT_OBJ_ROLE: case AUDIT_OBJ_TYPE: case AUDIT_OBJ_LEV_LOW: case AUDIT_OBJ_LEV_HIGH: /* The above note for AUDIT_SUBJ_USER...AUDIT_SUBJ_CLR
also applies here */ if (f->lsm_rule) { /* Find files that match */ if (name) {
result = security_audit_rule_match(
&name->oprop,
f->type,
f->op,
f->lsm_rule);
} elseif (ctx) {
list_for_each_entry(n, &ctx->names_list, list) { if (security_audit_rule_match(
&n->oprop,
f->type,
f->op,
f->lsm_rule)) {
++result; break;
}
}
} /* Find ipc objects that match */ if (!ctx || ctx->type != AUDIT_IPC) break; if (security_audit_rule_match(&ctx->ipc.oprop,
f->type, f->op,
f->lsm_rule))
++result;
} break; case AUDIT_ARG0: case AUDIT_ARG1: case AUDIT_ARG2: case AUDIT_ARG3: if (ctx)
result = audit_comparator(ctx->argv[f->type-AUDIT_ARG0], f->op, f->val); break; case AUDIT_FILTERKEY: /* ignore this field for filtering */
result = 1; break; case AUDIT_PERM:
result = audit_match_perm(ctx, f->val); if (f->op == Audit_not_equal)
result = !result; break; case AUDIT_FILETYPE:
result = audit_match_filetype(ctx, f->val); if (f->op == Audit_not_equal)
result = !result; break; case AUDIT_FIELD_COMPARE:
result = audit_field_compare(tsk, cred, f, ctx, name); break;
} if (!result) return 0;
}
if (ctx) { if (rule->filterkey) {
kfree(ctx->filterkey);
ctx->filterkey = kstrdup(rule->filterkey, GFP_ATOMIC);
}
ctx->prio = rule->prio;
} switch (rule->action) { case AUDIT_NEVER:
*state = AUDIT_STATE_DISABLED; break; case AUDIT_ALWAYS:
*state = AUDIT_STATE_RECORD; break;
} return 1;
}
/* At process creation time, we can determine if system-call auditing is * completely disabled for this task. Since we only have the task * structure at this point, we can only check uid and gid.
*/ staticenum audit_state audit_filter_task(struct task_struct *tsk, char **key)
{ struct audit_entry *e; enum audit_state state;
staticint audit_in_mask(conststruct audit_krule *rule, unsignedlong val)
{ int word, bit;
if (val > 0xffffffff) returnfalse;
word = AUDIT_WORD(val); if (word >= AUDIT_BITMASK_SIZE) returnfalse;
bit = AUDIT_BIT(val);
return rule->mask[word] & bit;
}
/** * __audit_filter_op - common filter helper for operations (syscall/uring/etc) * @tsk: associated task * @ctx: audit context * @list: audit filter list * @name: audit_name (can be NULL) * @op: current syscall/uring_op * * Run the udit filters specified in @list against @tsk using @ctx, * @name, and @op, as necessary; the caller is responsible for ensuring * that the call is made while the RCU read lock is held. The @name * parameter can be NULL, but all others must be specified. * Returns 1/true if the filter finds a match, 0/false if none are found.
*/ staticint __audit_filter_op(struct task_struct *tsk, struct audit_context *ctx, struct list_head *list, struct audit_names *name, unsignedlong op)
{ struct audit_entry *e; enum audit_state state;
/* At syscall exit time, this filter is called if the audit_state is * not low enough that auditing cannot take place, but is also not * high enough that we already know we have to write an audit record * (i.e., the state is AUDIT_STATE_BUILD).
*/ staticvoid audit_filter_syscall(struct task_struct *tsk, struct audit_context *ctx)
{ if (auditd_test_task(tsk)) return;
/* * Given an audit_name check the inode hash table to see if they match. * Called holding the rcu read lock to protect the use of audit_inode_hash
*/ staticint audit_filter_inode_name(struct task_struct *tsk, struct audit_names *n, struct audit_context *ctx)
{ int h = audit_hash_ino((u32)n->ino); struct list_head *list = &audit_inode_hash[h];
return __audit_filter_op(tsk, ctx, list, n, ctx->major);
}
/* At syscall exit time, this filter is called if any audit_names have been * collected during syscall processing. We only check rules in sublists at hash * buckets applicable to the inode numbers in audit_names. * Regarding audit_state, same rules apply as for audit_filter_syscall().
*/ void audit_filter_inodes(struct task_struct *tsk, struct audit_context *ctx)
{ struct audit_names *n;
if (auditd_test_task(tsk)) return;
rcu_read_lock();
list_for_each_entry(n, &ctx->names_list, list) { if (audit_filter_inode_name(tsk, n, ctx)) break;
}
rcu_read_unlock();
}
/** * audit_reset_context - reset a audit_context structure * @ctx: the audit_context to reset * * All fields in the audit_context will be reset to an initial state, all * references held by fields will be dropped, and private memory will be * released. When this function returns the audit_context will be suitable * for reuse, so long as the passed context is not NULL or a dummy context.
*/ staticvoid audit_reset_context(struct audit_context *ctx)
{ if (!ctx) return;
/* if ctx is non-null, reset the "ctx->context" regardless */
ctx->context = AUDIT_CTX_UNUSED; if (ctx->dummy) return;
/* * NOTE: It shouldn't matter in what order we release the fields, so * release them in the order in which they appear in the struct; * this gives us some hope of quickly making sure we are * resetting the audit_context properly. * * Other things worth mentioning: * - we don't reset "dummy" * - we don't reset "state", we do reset "current_state" * - we preserve "filterkey" if "state" is AUDIT_STATE_RECORD * - much of this is likely overkill, but play it safe for now * - we really need to work on improving the audit_context struct
*/
/** * audit_alloc - allocate an audit context block for a task * @tsk: task * * Filter on the task information and allocate a per-task audit context * if necessary. Doing so turns on system call auditing for the * specified task. This is called from copy_process, so no lock is * needed.
*/ int audit_alloc(struct task_struct *tsk)
{ struct audit_context *context; enum audit_state state; char *key = NULL;
if (likely(!audit_ever_enabled)) return 0;
state = audit_filter_task(tsk, &key); if (state == AUDIT_STATE_DISABLED) {
clear_task_syscall_work(tsk, SYSCALL_AUDIT); return 0;
}
context = audit_alloc_context(state); if (!context) {
kfree(key);
audit_log_lost("out of memory in audit_alloc"); return -ENOMEM;
}
context->filterkey = key;
staticinlinevoid audit_free_context(struct audit_context *context)
{ /* resetting is extra work, but it is likely just noise */
audit_reset_context(context);
audit_proctitle_free(context);
free_tree_refs(context);
kfree(context->filterkey);
kfree(context);
}
staticvoid audit_log_execve_info(struct audit_context *context, struct audit_buffer **ab)
{ long len_max; long len_rem; long len_full; long len_buf; long len_abuf = 0; long len_tmp; bool require_data; bool encode; unsignedint iter; unsignedint arg; char *buf_head; char *buf; constchar __user *p = (constchar __user *)current->mm->arg_start;
/* NOTE: this buffer needs to be large enough to hold all the non-arg * data we put in the audit record for this argument (see the
* code below) ... at this point in time 96 is plenty */ char abuf[96];
/* NOTE: we set MAX_EXECVE_AUDIT_LEN to a rather arbitrary limit, the * current value of 7500 is not as important as the fact that it * is less than 8k, a setting of 7500 gives us plenty of wiggle
* room if we go over a little bit in the logging below */
WARN_ON_ONCE(MAX_EXECVE_AUDIT_LEN > 7500);
len_max = MAX_EXECVE_AUDIT_LEN;
/* scratch buffer to hold the userspace args */
buf_head = kmalloc(MAX_EXECVE_AUDIT_LEN + 1, GFP_KERNEL); if (!buf_head) {
audit_panic("out of memory for argv string"); return;
}
buf = buf_head;
len_rem = len_max;
len_buf = 0;
len_full = 0;
require_data = true;
encode = false;
iter = 0;
arg = 0; do { /* NOTE: we don't ever want to trust this value for anything * serious, but the audit record format insists we * provide an argument length for really long arguments, * e.g. > MAX_EXECVE_AUDIT_LEN, so we have no choice but * to use strncpy_from_user() to obtain this value for * recording in the log, although we don't use it
* anywhere here to avoid a double-fetch problem */ if (len_full == 0)
len_full = strnlen_user(p, MAX_ARG_STRLEN) - 1;
/* read more data from userspace */ if (require_data) { /* can we make more room in the buffer? */ if (buf != buf_head) {
memmove(buf_head, buf, len_buf);
buf = buf_head;
}
/* fetch as much as we can of the argument */
len_tmp = strncpy_from_user(&buf_head[len_buf], p,
len_max - len_buf); if (len_tmp == -EFAULT) { /* unable to copy from userspace */
send_sig(SIGKILL, current, 0); goto out;
} elseif (len_tmp == (len_max - len_buf)) { /* buffer is not large enough */
require_data = true; /* NOTE: if we are going to span multiple * buffers force the encoding so we stand * a chance at a sane len_full value and
* consistent record encoding */
encode = true;
len_full = len_full * 2;
p += len_tmp;
} else {
require_data = false; if (!encode)
encode = audit_string_contains_control(
buf, len_tmp); /* try to use a trusted value for len_full */ if (len_full < len_max)
len_full = (encode ?
len_tmp * 2 : len_tmp);
p += len_tmp + 1;
}
len_buf += len_tmp;
buf_head[len_buf] = '\0';
/* length of the buffer in the audit record? */
len_abuf = (encode ? len_buf * 2 : len_buf + 2);
}
/* write as much as we can to the audit log */ if (len_buf >= 0) { /* NOTE: some magic numbers here - basically if we * can't fit a reasonable amount of data into the * existing audit buffer, flush it and start with
* a new buffer */ if ((sizeof(abuf) + 8) > len_rem) {
len_rem = len_max;
audit_log_end(*ab);
*ab = audit_log_start(context,
GFP_KERNEL, AUDIT_EXECVE); if (!*ab) goto out;
}
/* log the arg in the audit record */
audit_log_format(*ab, "%s", abuf);
len_rem -= len_tmp;
len_tmp = len_buf; if (encode) { if (len_abuf > len_rem)
len_tmp = len_rem / 2; /* encoding */
audit_log_n_hex(*ab, buf, len_tmp);
len_rem -= len_tmp * 2;
len_abuf -= len_tmp * 2;
} else { if (len_abuf > len_rem)
len_tmp = len_rem - 2; /* quotes */
audit_log_n_string(*ab, buf, len_tmp);
len_rem -= len_tmp + 2; /* don't subtract the "2" because we still need
* to add quotes to the remaining string */
len_abuf -= len_tmp;
}
len_buf -= len_tmp;
buf += len_tmp;
}
/* ready to move to the next argument? */ if ((len_buf == 0) && !require_data) {
arg++;
iter = 0;
len_full = 0;
require_data = true;
encode = false;
}
} while (arg < context->execve.argc);
/* NOTE: the caller handles the final audit_log_end() call */
break; case AUDIT_TIME_ADJNTPVAL: case AUDIT_TIME_INJOFFSET: /* this call deviates from the rest, eating the buffer */
audit_log_time(context, &ab); break;
}
audit_log_end(ab);
}
staticinlineint audit_proctitle_rtrim(char *proctitle, int len)
{ char *end = proctitle + len - 1;
while (end > proctitle && !isprint(*end))
end--;
/* catch the case where proctitle is only 1 non-print character */
len = end - proctitle + 1;
len -= isprint(proctitle[len-1]) == 0; return len;
}
/* * audit_log_name - produce AUDIT_PATH record from struct audit_names * @context: audit_context for the task * @n: audit_names structure with reportable details * @path: optional path to report instead of audit_names->name * @record_num: record number to report when handling a list of names * @call_panic: optional pointer to int that will be updated if secid fails
*/ staticvoid audit_log_name(struct audit_context *context, struct audit_names *n, conststruct path *path, int record_num, int *call_panic)
{ struct audit_buffer *ab;
ab = audit_log_start(context, GFP_KERNEL, AUDIT_PATH); if (!ab) return;
audit_log_format(ab, "item=%d", record_num);
if (path)
audit_log_d_path(ab, " name=", path); elseif (n->name) { switch (n->name_len) { case AUDIT_NAME_FULL: /* log the full path */
audit_log_format(ab, " name=");
audit_log_untrustedstring(ab, n->name->name); break; case 0: /* name was specified as a relative path and the * directory component is the cwd
*/ if (context->pwd.dentry && context->pwd.mnt)
audit_log_d_path(ab, " name=", &context->pwd); else
audit_log_format(ab, " name=(null)"); break; default: /* log the name's directory component */
audit_log_format(ab, " name=");
audit_log_n_untrustedstring(ab, n->name->name,
n->name_len);
}
} else
audit_log_format(ab, " name=(null)");
if (context->type)
show_special(context, &call_panic);
if (context->fds[0] >= 0) {
ab = audit_log_start(context, GFP_KERNEL, AUDIT_FD_PAIR); if (ab) {
audit_log_format(ab, "fd0=%d fd1=%d",
context->fds[0], context->fds[1]);
audit_log_end(ab);
}
}
if (context->sockaddr_len) {
ab = audit_log_start(context, GFP_KERNEL, AUDIT_SOCKADDR); if (ab) {
audit_log_format(ab, "saddr=");
audit_log_n_hex(ab, (void *)context->sockaddr,
context->sockaddr_len);
audit_log_end(ab);
}
}
for (aux = context->aux_pids; aux; aux = aux->next) { struct audit_aux_data_pids *axs = (void *)aux;
for (i = 0; i < axs->pid_count; i++) if (audit_log_pid_context(context, axs->target_pid[i],
axs->target_auid[i],
axs->target_uid[i],
axs->target_sessionid[i],
&axs->target_ref[i],
axs->target_comm[i]))
call_panic = 1;
}
if (context->pwd.dentry && context->pwd.mnt) {
ab = audit_log_start(context, GFP_KERNEL, AUDIT_CWD); if (ab) {
audit_log_d_path(ab, "cwd=", &context->pwd);
audit_log_end(ab);
}
}
i = 0;
list_for_each_entry(n, &context->names_list, list) { if (n->hidden) continue;
audit_log_name(context, n, NULL, i++, &call_panic);
}
if (context->context == AUDIT_CTX_SYSCALL)
audit_log_proctitle();
/* Send end of event record to help user space know we are finished */
ab = audit_log_start(context, GFP_KERNEL, AUDIT_EOE); if (ab)
audit_log_end(ab); if (call_panic)
audit_panic("error in audit_log_exit()");
}
/** * __audit_free - free a per-task audit context * @tsk: task whose audit context block to free * * Called from copy_process, do_exit, and the io_uring code
*/ void __audit_free(struct task_struct *tsk)
{ struct audit_context *context = tsk->audit_context;
if (!context) return;
/* this may generate CONFIG_CHANGE records */ if (!list_empty(&context->killed_trees))
audit_kill_trees(context);
/* We are called either by do_exit() or the fork() error handling code; * in the former case tsk == current and in the latter tsk is a * random task_struct that doesn't have any meaningful data we * need to log via audit_log_exit().
*/ if (tsk == current && !context->dummy) {
context->return_valid = AUDITSC_INVALID;
context->return_code = 0; if (context->context == AUDIT_CTX_SYSCALL) {
audit_filter_syscall(tsk, context);
audit_filter_inodes(tsk, context); if (context->current_state == AUDIT_STATE_RECORD)
audit_log_exit();
} elseif (context->context == AUDIT_CTX_URING) { /* TODO: verify this case is real and valid */
audit_filter_uring(tsk, context);
audit_filter_inodes(tsk, context); if (context->current_state == AUDIT_STATE_RECORD)
audit_log_uring(context);
}
}
/** * audit_return_fixup - fixup the return codes in the audit_context * @ctx: the audit_context * @success: true/false value to indicate if the operation succeeded or not * @code: operation return code * * We need to fixup the return code in the audit logs if the actual return * codes are later going to be fixed by the arch specific signal handlers.
*/ staticvoid audit_return_fixup(struct audit_context *ctx, int success, long code)
{ /* * This is actually a test for: * (rc == ERESTARTSYS ) || (rc == ERESTARTNOINTR) || * (rc == ERESTARTNOHAND) || (rc == ERESTART_RESTARTBLOCK) * * but is faster than a bunch of ||
*/ if (unlikely(code <= -ERESTARTSYS) &&
(code >= -ERESTART_RESTARTBLOCK) &&
(code != -ENOIOCTLCMD))
ctx->return_code = -EINTR; else
ctx->return_code = code;
ctx->return_valid = (success ? AUDITSC_SUCCESS : AUDITSC_FAILURE);
}
/** * __audit_uring_entry - prepare the kernel task's audit context for io_uring * @op: the io_uring opcode * * This is similar to audit_syscall_entry() but is intended for use by io_uring * operations. This function should only ever be called from * audit_uring_entry() as we rely on the audit context checking present in that * function.
*/ void __audit_uring_entry(u8 op)
{ struct audit_context *ctx = audit_context();
if (ctx->state == AUDIT_STATE_DISABLED) return;
/* * NOTE: It's possible that we can be called from the process' context * before it returns to userspace, and before audit_syscall_exit() * is called. In this case there is not much to do, just record * the io_uring details and return.
*/
ctx->uring_op = op; if (ctx->context == AUDIT_CTX_SYSCALL) return;
/** * __audit_uring_exit - wrap up the kernel task's audit context after io_uring * @success: true/false value to indicate if the operation succeeded or not * @code: operation return code * * This is similar to audit_syscall_exit() but is intended for use by io_uring * operations. This function should only ever be called from * audit_uring_exit() as we rely on the audit context checking present in that * function.
*/ void __audit_uring_exit(int success, long code)
{ struct audit_context *ctx = audit_context();
if (ctx->dummy) { if (ctx->context != AUDIT_CTX_URING) return; goto out;
}
audit_return_fixup(ctx, success, code); if (ctx->context == AUDIT_CTX_SYSCALL) { /* * NOTE: See the note in __audit_uring_entry() about the case * where we may be called from process context before we * return to userspace via audit_syscall_exit(). In this * case we simply emit a URINGOP record and bail, the * normal syscall exit handling will take care of * everything else. * It is also worth mentioning that when we are called, * the current process creds may differ from the creds * used during the normal syscall processing; keep that * in mind if/when we move the record generation code.
*/
/* * We need to filter on the syscall info here to decide if we * should emit a URINGOP record. I know it seems odd but this * solves the problem where users have a filter to block *all* * syscall records in the "exit" filter; we want to preserve * the behavior here.
*/
audit_filter_syscall(current, ctx); if (ctx->current_state != AUDIT_STATE_RECORD)
audit_filter_uring(current, ctx);
audit_filter_inodes(current, ctx); if (ctx->current_state != AUDIT_STATE_RECORD) return;
audit_log_uring(ctx); return;
}
/* this may generate CONFIG_CHANGE records */ if (!list_empty(&ctx->killed_trees))
audit_kill_trees(ctx);
/* run through both filters to ensure we set the filterkey properly */
audit_filter_uring(current, ctx);
audit_filter_inodes(current, ctx); if (ctx->current_state != AUDIT_STATE_RECORD) goto out;
audit_log_exit();
out:
audit_reset_context(ctx);
}
/** * __audit_syscall_entry - fill in an audit record at syscall entry * @major: major syscall type (function) * @a1: additional syscall register 1 * @a2: additional syscall register 2 * @a3: additional syscall register 3 * @a4: additional syscall register 4 * * Fill in audit context at syscall entry. This only happens if the * audit context was created when the task was created and the state or * filters demand the audit context be built. If the state from the * per-task filter or from the per-syscall filter is AUDIT_STATE_RECORD, * then the record will be written at syscall exit time (otherwise, it * will only be written if another part of the kernel requests that it * be written).
*/ void __audit_syscall_entry(int major, unsignedlong a1, unsignedlong a2, unsignedlong a3, unsignedlong a4)
{ struct audit_context *context = audit_context(); enum audit_state state;
if (!audit_enabled || !context) return;
WARN_ON(context->context != AUDIT_CTX_UNUSED);
WARN_ON(context->name_count); if (context->context != AUDIT_CTX_UNUSED || context->name_count) {
audit_panic("unrecoverable error in audit_syscall_entry()"); return;
}
state = context->state; if (state == AUDIT_STATE_DISABLED) return;
context->dummy = !audit_n_rules; if (!context->dummy && state == AUDIT_STATE_BUILD) {
context->prio = 0; if (auditd_test_task(current)) return;
}
/** * __audit_syscall_exit - deallocate audit context after a system call * @success: success value of the syscall * @return_code: return value of the syscall * * Tear down after system call. If the audit context has been marked as * auditable (either because of the AUDIT_STATE_RECORD state from * filtering, or because some other part of the kernel wrote an audit * message), then write out the syscall information. In call cases, * free the names stored from getname().
*/ void __audit_syscall_exit(int success, long return_code)
{ struct audit_context *context = audit_context();
if (!context || context->dummy ||
context->context != AUDIT_CTX_SYSCALL) goto out;
/* this may generate CONFIG_CHANGE records */ if (!list_empty(&context->killed_trees))
audit_kill_trees(context);
audit_return_fixup(context, success, return_code); /* run through both filters to ensure we set the filterkey properly */
audit_filter_syscall(current, context);
audit_filter_inodes(current, context); if (context->current_state != AUDIT_STATE_RECORD) goto out;
if (likely(!inode->i_fsnotify_marks)) return;
context = audit_context();
p = context->trees;
count = context->tree_count;
rcu_read_lock();
chunk = audit_tree_lookup(inode);
rcu_read_unlock(); if (!chunk) return; if (likely(put_tree_ref(context, chunk))) return; if (unlikely(!grow_tree_refs(context))) {
pr_warn("out of memory, audit has lost a tree reference\n");
audit_set_auditable(context);
audit_put_chunk(chunk);
unroll_tree_refs(context, p, count); return;
}
put_tree_ref(context, chunk);
}
context = audit_context();
p = context->trees;
count = context->tree_count;
retry:
drop = NULL;
d = dentry;
rcu_read_lock();
seq = read_seqbegin(&rename_lock); for (;;) { struct inode *inode = d_backing_inode(d);
if (inode && unlikely(inode->i_fsnotify_marks)) { struct audit_chunk *chunk;
chunk = audit_tree_lookup(inode); if (chunk) { if (unlikely(!put_tree_ref(context, chunk))) {
drop = chunk; break;
}
}
}
parent = d->d_parent; if (parent == d) break;
d = parent;
} if (unlikely(read_seqretry(&rename_lock, seq) || drop)) { /* in this order */
rcu_read_unlock(); if (!drop) { /* just a race with rename */
unroll_tree_refs(context, p, count); goto retry;
}
audit_put_chunk(drop); if (grow_tree_refs(context)) { /* OK, got more space */
unroll_tree_refs(context, p, count); goto retry;
} /* too bad */
pr_warn("out of memory, audit has lost a tree reference\n");
unroll_tree_refs(context, p, count);
audit_set_auditable(context); return;
}
rcu_read_unlock();
}
context->name_count++; if (!context->pwd.dentry)
get_fs_pwd(current->fs, &context->pwd); return aname;
}
/** * __audit_reusename - fill out filename with info from existing entry * @uptr: userland ptr to pathname * * Search the audit_names list for the current audit context. If there is an * existing entry with a matching "uptr" then return the filename * associated with that audit_name. If not, return NULL.
*/ struct filename *
__audit_reusename(const __user char *uptr)
{ struct audit_context *context = audit_context(); struct audit_names *n;
list_for_each_entry(n, &context->names_list, list) { if (!n->name) continue; if (n->name->uptr == uptr) return refname(n->name);
} return NULL;
}
/** * __audit_getname - add a name to the list * @name: name to add * * Add a name to the list of audit names for this context. * Called from fs/namei.c:getname().
*/ void __audit_getname(struct filename *name)
{ struct audit_context *context = audit_context(); struct audit_names *n;
if (context->context == AUDIT_CTX_UNUSED) return;
n = audit_alloc_name(context, AUDIT_TYPE_UNKNOWN); if (!n) return;
/* * If we have a pointer to an audit_names entry already, then we can * just use it directly if the type is correct.
*/
n = name->aname; if (n) { if (parent) { if (n->type == AUDIT_TYPE_PARENT ||
n->type == AUDIT_TYPE_UNKNOWN) goto out;
} else { if (n->type != AUDIT_TYPE_PARENT) goto out;
}
}
list_for_each_entry_reverse(n, &context->names_list, list) { if (n->ino) { /* valid inode number, use that for the comparison */ if (n->ino != inode->i_ino ||
n->dev != inode->i_sb->s_dev) continue;
} elseif (n->name) { /* inode number has not been set, check the name */ if (strcmp(n->name->name, name->name)) continue;
} else /* no inode and no name (?!) ... this is odd ... */ continue;
/* match the correct record type */ if (parent) { if (n->type == AUDIT_TYPE_PARENT ||
n->type == AUDIT_TYPE_UNKNOWN) goto out;
} else { if (n->type != AUDIT_TYPE_PARENT) goto out;
}
}
out_alloc: /* unable to find an entry with both a matching name and type */
n = audit_alloc_name(context, AUDIT_TYPE_UNKNOWN); if (!n) return; if (name) {
n->name = name;
refname(name);
}
out: if (parent) {
--> --------------------
--> maximum size reached
--> --------------------
¤ Dauer der Verarbeitung: 0.47 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.