From 4617721c502b2ddaa4e324e86da4997edf738fa5 Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Wed, 2 Sep 2026 09:55:01 -0400 Subject: ftrace: Synchronize the initialization of ftrace_ops There's some internal state that ftrace_ops needs to have set, but since it can be declared outside of the ftrace.c code, it calls ftrace_ops_init() on the ops in every global function. The issue is that if two tasks call it on the same ops at the same time it is possible to have the initialization of one corrupt the initialization of the other call. Create a ops_mutex to use to synchronize every initialization of the ftrace_ops. The mutex is taken within checking the ftrace_ops flag that states it was initializied but the flag is checked again after the mutex has been taken. Checking first outside the mutex allows it to shortcut having to take the mutex. But then the check needs to be done again after the mute is taken in case of races. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260902095501.6b59af20@gandalf.local.home Fixes: f04f24fb7e48d ("ftrace, kprobes: Fix a deadlock on ftrace_regex_lock") Reported-by: sashiko-bot@kernel.org Close: https://lore.kernel.org/all/20260829025528.49A831F000E9@smtp.kernel.org/ Signed-off-by: Steven Rostedt --- drivers/tty/tty_audit.c | 251 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 251 insertions(+) create mode 100644 drivers/tty/tty_audit.c (limited to 'drivers/tty/tty_audit.c') diff --git a/drivers/tty/tty_audit.c b/drivers/tty/tty_audit.c new file mode 100644 index 000000000..d014af6ab --- /dev/null +++ b/drivers/tty/tty_audit.c @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Creating audit events from TTY input. + * + * Copyright (C) 2007 Red Hat, Inc. All rights reserved. + * + * Authors: Miloslav Trmac + */ + +#include +#include +#include +#include "tty.h" + +#define TTY_AUDIT_BUF_SIZE 4096 + +struct tty_audit_buf { + struct mutex mutex; /* Protects all data below */ + dev_t dev; /* The TTY which the data is from */ + bool icanon; + size_t valid; + u8 *data; /* Allocated size TTY_AUDIT_BUF_SIZE */ +}; + +static struct tty_audit_buf *tty_audit_buf_ref(void) +{ + struct tty_audit_buf *buf; + + buf = current->signal->tty_audit_buf; + WARN_ON(buf == ERR_PTR(-ESRCH)); + return buf; +} + +static struct tty_audit_buf *tty_audit_buf_alloc(void) +{ + struct tty_audit_buf *buf; + + buf = kzalloc_obj(*buf); + if (!buf) + goto err; + + buf->data = kmalloc(TTY_AUDIT_BUF_SIZE, GFP_KERNEL); + if (!buf->data) + goto err_buf; + + mutex_init(&buf->mutex); + + return buf; + +err_buf: + kfree(buf); +err: + return NULL; +} + +static void tty_audit_buf_free(struct tty_audit_buf *buf) +{ + WARN_ON(buf->valid != 0); + kfree(buf->data); + kfree(buf); +} + +static void tty_audit_log(const char *description, dev_t dev, + const u8 *data, size_t size) +{ + struct audit_buffer *ab; + pid_t pid = task_pid_nr(current); + uid_t uid = from_kuid(&init_user_ns, task_uid(current)); + uid_t loginuid = from_kuid(&init_user_ns, audit_get_loginuid(current)); + unsigned int sessionid = audit_get_sessionid(current); + char name[TASK_COMM_LEN]; + + ab = audit_log_start(audit_context(), GFP_KERNEL, AUDIT_TTY); + if (!ab) + return; + + audit_log_format(ab, "%s pid=%u uid=%u auid=%u ses=%u major=%d minor=%d comm=", + description, pid, uid, loginuid, sessionid, + MAJOR(dev), MINOR(dev)); + get_task_comm(name, current); + audit_log_untrustedstring(ab, name); + audit_log_format(ab, " data="); + audit_log_n_hex(ab, data, size); + audit_log_end(ab); +} + +/* + * tty_audit_buf_push - Push buffered data out + * + * Generate an audit message from the contents of @buf, which is owned by + * the current task. @buf->mutex must be locked. + */ +static void tty_audit_buf_push(struct tty_audit_buf *buf) +{ + if (buf->valid == 0) + return; + if (audit_enabled == AUDIT_OFF) { + buf->valid = 0; + return; + } + tty_audit_log("tty", buf->dev, buf->data, buf->valid); + buf->valid = 0; +} + +/** + * tty_audit_exit - Handle a task exit + * + * Make sure all buffered data is written out and deallocate the buffer. + * Only needs to be called if current->signal->tty_audit_buf != %NULL. + * + * The process is single-threaded at this point; no other threads share + * current->signal. + */ +void tty_audit_exit(void) +{ + struct tty_audit_buf *buf; + + buf = xchg(¤t->signal->tty_audit_buf, ERR_PTR(-ESRCH)); + if (!buf) + return; + + tty_audit_buf_push(buf); + tty_audit_buf_free(buf); +} + +/* + * tty_audit_fork - Copy TTY audit state for a new task + * + * Set up TTY audit state in @sig from current. @sig needs no locking. + */ +void tty_audit_fork(struct signal_struct *sig) +{ + sig->audit_tty = current->signal->audit_tty; +} + +/* + * tty_audit_tiocsti - Log TIOCSTI + */ +void tty_audit_tiocsti(const struct tty_struct *tty, u8 ch) +{ + dev_t dev; + + dev = MKDEV(tty->driver->major, tty->driver->minor_start) + tty->index; + if (tty_audit_push()) + return; + + if (audit_enabled) + tty_audit_log("ioctl=TIOCSTI", dev, &ch, 1); +} + +/* + * tty_audit_push - Flush current's pending audit data + * + * Returns 0 if success, -EPERM if tty audit is disabled + */ +int tty_audit_push(void) +{ + struct tty_audit_buf *buf; + + if (~current->signal->audit_tty & AUDIT_TTY_ENABLE) + return -EPERM; + + buf = tty_audit_buf_ref(); + if (!IS_ERR_OR_NULL(buf)) { + mutex_lock(&buf->mutex); + tty_audit_buf_push(buf); + mutex_unlock(&buf->mutex); + } + return 0; +} + +/* + * tty_audit_buf_get - Get an audit buffer. + * + * Get an audit buffer, allocate it if necessary. Return %NULL + * if out of memory or ERR_PTR(-ESRCH) if tty_audit_exit() has already + * occurred. Otherwise, return a new reference to the buffer. + */ +static struct tty_audit_buf *tty_audit_buf_get(void) +{ + struct tty_audit_buf *buf; + + buf = tty_audit_buf_ref(); + if (buf) + return buf; + + buf = tty_audit_buf_alloc(); + if (buf == NULL) { + audit_log_lost("out of memory in TTY auditing"); + return NULL; + } + + /* Race to use this buffer, free it if another wins */ + if (cmpxchg(¤t->signal->tty_audit_buf, NULL, buf) != NULL) + tty_audit_buf_free(buf); + return tty_audit_buf_ref(); +} + +/* + * tty_audit_add_data - Add data for TTY auditing. + * + * Audit @data of @size from @tty, if necessary. + */ +void tty_audit_add_data(const struct tty_struct *tty, const void *data, + size_t size) +{ + struct tty_audit_buf *buf; + unsigned int audit_tty; + bool icanon = L_ICANON(tty); + dev_t dev; + + audit_tty = READ_ONCE(current->signal->audit_tty); + if (~audit_tty & AUDIT_TTY_ENABLE) + return; + + if (unlikely(size == 0)) + return; + + if (tty->driver->type == TTY_DRIVER_TYPE_PTY + && tty->driver->subtype == PTY_TYPE_MASTER) + return; + + if ((~audit_tty & AUDIT_TTY_LOG_PASSWD) && icanon && !L_ECHO(tty)) + return; + + buf = tty_audit_buf_get(); + if (IS_ERR_OR_NULL(buf)) + return; + + mutex_lock(&buf->mutex); + dev = MKDEV(tty->driver->major, tty->driver->minor_start) + tty->index; + if (buf->dev != dev || buf->icanon != icanon) { + tty_audit_buf_push(buf); + buf->dev = dev; + buf->icanon = icanon; + } + do { + size_t run; + + run = TTY_AUDIT_BUF_SIZE - buf->valid; + if (run > size) + run = size; + memcpy(buf->data + buf->valid, data, run); + buf->valid += run; + data += run; + size -= run; + if (buf->valid == TTY_AUDIT_BUF_SIZE) + tty_audit_buf_push(buf); + } while (size != 0); + mutex_unlock(&buf->mutex); +} -- cgit v1.3.1