1683 lines · 40.4 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0#include <trace/syscall.h>#include <trace/events/syscalls.h>#include <linux/kernel_stat.h>#include <linux/syscalls.h>#include <linux/slab.h>#include <linux/kernel.h>#include <linux/module.h>	/* for MODULE_NAME_LEN via KSYM_SYMBOL_LEN */#include <linux/ftrace.h>#include <linux/perf_event.h>#include <linux/xarray.h>#include <asm/syscall.h> #include "trace_output.h"#include "trace.h" static DEFINE_MUTEX(syscall_trace_lock); static int syscall_enter_register(struct trace_event_call *event,				 enum trace_reg type, void *data);static int syscall_exit_register(struct trace_event_call *event,				 enum trace_reg type, void *data); static struct list_head *syscall_get_enter_fields(struct trace_event_call *call){	struct syscall_metadata *entry = call->data; 	return &entry->enter_fields;} extern struct syscall_metadata *__start_syscalls_metadata[];extern struct syscall_metadata *__stop_syscalls_metadata[]; static DEFINE_XARRAY(syscalls_metadata_sparse);static struct syscall_metadata **syscalls_metadata; #ifndef ARCH_HAS_SYSCALL_MATCH_SYM_NAMEstatic inline bool arch_syscall_match_sym_name(const char *sym, const char *name){	/*	 * Only compare after the "sys" prefix. Archs that use	 * syscall wrappers may have syscalls symbols aliases prefixed	 * with ".SyS" or ".sys" instead of "sys", leading to an unwanted	 * mismatch.	 */	return !strcmp(sym + 3, name + 3);}#endif #ifdef ARCH_TRACE_IGNORE_COMPAT_SYSCALLS/* * Some architectures that allow for 32bit applications * to run on a 64bit kernel, do not map the syscalls for * the 32bit tasks the same as they do for 64bit tasks. * *     *cough*x86*cough* * * In such a case, instead of reporting the wrong syscalls, * simply ignore them. * * For an arch to ignore the compat syscalls it needs to * define ARCH_TRACE_IGNORE_COMPAT_SYSCALLS as well as * define the function arch_trace_is_compat_syscall() to let * the tracing system know that it should ignore it. */static inttrace_get_syscall_nr(struct task_struct *task, struct pt_regs *regs){	if (unlikely(arch_trace_is_compat_syscall(regs)))		return -1; 	return syscall_get_nr(task, regs);}#elsestatic inline inttrace_get_syscall_nr(struct task_struct *task, struct pt_regs *regs){	return syscall_get_nr(task, regs);}#endif /* ARCH_TRACE_IGNORE_COMPAT_SYSCALLS */ static __init struct syscall_metadata *find_syscall_meta(unsigned long syscall){	struct syscall_metadata **start;	struct syscall_metadata **stop;	char str[KSYM_SYMBOL_LEN];  	start = __start_syscalls_metadata;	stop = __stop_syscalls_metadata;	kallsyms_lookup(syscall, NULL, NULL, NULL, str); 	if (arch_syscall_match_sym_name(str, "sys_ni_syscall"))		return NULL; 	for ( ; start < stop; start++) {		if ((*start)->name && arch_syscall_match_sym_name(str, (*start)->name))			return *start;	}	return NULL;} static struct syscall_metadata *syscall_nr_to_meta(int nr){	if (IS_ENABLED(CONFIG_HAVE_SPARSE_SYSCALL_NR))		return xa_load(&syscalls_metadata_sparse, (unsigned long)nr); 	if (!syscalls_metadata || nr >= NR_syscalls || nr < 0)		return NULL; 	return syscalls_metadata[nr];} const char *get_syscall_name(int syscall){	struct syscall_metadata *entry; 	entry = syscall_nr_to_meta(syscall);	if (!entry)		return NULL; 	return entry->name;} /* Added to user strings or arrays when max limit is reached */#define EXTRA "..." static void get_dynamic_len_ptr(struct syscall_trace_enter *trace,				struct syscall_metadata *entry,				int *offset_p, int *len_p, unsigned char **ptr_p){	unsigned char *ptr;	int offset = *offset_p;	int val; 	/* This arg points to a user space string */	ptr = (void *)trace->args + sizeof(long) * entry->nb_args + offset;	val = *(int *)ptr; 	/* The value is a dynamic string (len << 16 | offset) */	ptr = (void *)trace + (val & 0xffff);	*len_p = val >> 16;	offset += 4; 	*ptr_p = ptr;	*offset_p = offset;} static enum print_line_tsys_enter_openat_print(struct syscall_trace_enter *trace, struct syscall_metadata *entry,		       struct trace_seq *s, struct trace_event *event){	unsigned char *ptr;	int offset = 0;	int bits, len;	bool done = false;	static const struct trace_print_flags __flags[] =		{			{ O_TMPFILE, "O_TMPFILE" },			{ O_WRONLY, "O_WRONLY" },			{ O_RDWR, "O_RDWR" },			{ O_CREAT, "O_CREAT" },			{ O_EXCL, "O_EXCL" },			{ O_NOCTTY, "O_NOCTTY" },			{ O_TRUNC, "O_TRUNC" },			{ O_APPEND, "O_APPEND" },			{ O_NONBLOCK, "O_NONBLOCK" },			{ O_DSYNC, "O_DSYNC" },			{ O_DIRECT, "O_DIRECT" },			{ O_LARGEFILE, "O_LARGEFILE" },			{ O_DIRECTORY, "O_DIRECTORY" },			{ O_NOFOLLOW, "O_NOFOLLOW" },			{ O_NOATIME, "O_NOATIME" },			{ O_CLOEXEC, "O_CLOEXEC" },		}; 	trace_seq_printf(s, "%s(", entry->name); 	for (int i = 0; !done && i < entry->nb_args; i++) { 		if (trace_seq_has_overflowed(s))			goto end; 		if (i)			trace_seq_puts(s, ", "); 		switch (i) {		case 2:			bits = trace->args[2]; 			trace_seq_puts(s, "flags: "); 			/* No need to show mode when not creating the file */			if (!(bits & (O_CREAT|O_TMPFILE)))				done = true; 			if (!(bits & O_ACCMODE)) {				if (!bits) {					trace_seq_puts(s, "O_RDONLY");					continue;				}				trace_seq_puts(s, "O_RDONLY|");			} 			trace_print_flags_seq(s, "|", bits, __flags, ARRAY_SIZE(__flags));			/*			 * trace_print_flags_seq() adds a '\0' to the			 * buffer, but this needs to append more to the seq.			 */			if (!trace_seq_has_overflowed(s))				trace_seq_pop(s); 			continue;		case 3:			trace_seq_printf(s, "%s: 0%03o", entry->args[i],					 (unsigned int)trace->args[i]);			continue;		} 		trace_seq_printf(s, "%s: %lu", entry->args[i],				 trace->args[i]); 		if (!(BIT(i) & entry->user_mask))			continue; 		get_dynamic_len_ptr(trace, entry, &offset, &len, &ptr);		trace_seq_printf(s, " \"%.*s\"", len, ptr);	} 	trace_seq_putc(s, ')');end:	trace_seq_putc(s, '\n'); 	return trace_handle_return(s);} static enum print_line_tprint_syscall_enter(struct trace_iterator *iter, int flags,		    struct trace_event *event){	struct trace_array *tr = iter->tr;	struct trace_seq *s = &iter->seq;	struct trace_entry *ent = iter->ent;	struct syscall_trace_enter *trace;	struct syscall_metadata *entry;	int i, syscall, val, len;	unsigned char *ptr;	int offset = 0; 	trace = (typeof(trace))ent;	syscall = trace->nr;	entry = syscall_nr_to_meta(syscall); 	if (!entry)		goto end; 	if (entry->enter_event->event.type != ent->type) {		WARN_ON_ONCE(1);		goto end;	} 	switch (entry->syscall_nr) {	case __NR_openat:		if (!tr || !(tr->trace_flags & TRACE_ITER(VERBOSE)))			return sys_enter_openat_print(trace, entry, s, event);		break;	default:		break;	} 	trace_seq_printf(s, "%s(", entry->name); 	for (i = 0; i < entry->nb_args; i++) {		bool printable = false;		char *str; 		if (trace_seq_has_overflowed(s))			goto end; 		if (i)			trace_seq_puts(s, ", "); 		/* parameter types */		if (tr && tr->trace_flags & TRACE_ITER(VERBOSE))			trace_seq_printf(s, "%s ", entry->types[i]); 		/* parameter values */		if (trace->args[i] < 10)			trace_seq_printf(s, "%s: %lu", entry->args[i],					 trace->args[i]);		else			trace_seq_printf(s, "%s: 0x%lx", entry->args[i],					 trace->args[i]); 		if (!(BIT(i) & entry->user_mask))			continue; 		get_dynamic_len_ptr(trace, entry, &offset, &len, &ptr); 		if (entry->user_arg_size < 0 || entry->user_arg_is_str) {			trace_seq_printf(s, " \"%.*s\"", len, ptr);			continue;		} 		val = trace->args[entry->user_arg_size]; 		str = ptr;		trace_seq_puts(s, " (");		for (int x = 0; x < len; x++, ptr++) {			if (isascii(*ptr) && isprint(*ptr))				printable = true;			if (x)				trace_seq_putc(s, ':');			trace_seq_printf(s, "%02x", *ptr);		}		if (len < val)			trace_seq_printf(s, ", %s", EXTRA); 		trace_seq_putc(s, ')'); 		/* If nothing is printable, don't bother printing anything */		if (!printable)			continue; 		trace_seq_puts(s, " \"");		for (int x = 0; x < len; x++) {			if (isascii(str[x]) && isprint(str[x]))				trace_seq_putc(s, str[x]);			else				trace_seq_putc(s, '.');		}		if (len < val)			trace_seq_printf(s, "\"%s", EXTRA);		else			trace_seq_putc(s, '"');	} 	trace_seq_putc(s, ')');end:	trace_seq_putc(s, '\n'); 	return trace_handle_return(s);} static enum print_line_tprint_syscall_exit(struct trace_iterator *iter, int flags,		   struct trace_event *event){	struct trace_seq *s = &iter->seq;	struct trace_entry *ent = iter->ent;	struct syscall_trace_exit *trace;	int syscall;	struct syscall_metadata *entry; 	trace = (typeof(trace))ent;	syscall = trace->nr;	entry = syscall_nr_to_meta(syscall); 	if (!entry) {		trace_seq_putc(s, '\n');		goto out;	} 	if (entry->exit_event->event.type != ent->type) {		WARN_ON_ONCE(1);		return TRACE_TYPE_UNHANDLED;	} 	trace_seq_printf(s, "%s -> 0x%lx\n", entry->name,				trace->ret);  out:	return trace_handle_return(s);} #define SYSCALL_FIELD(_type, _name) {					\	.type = #_type, .name = #_name,					\	.size = sizeof(_type), .align = __alignof__(_type),		\	.is_signed = is_signed_type(_type), .filter_type = FILTER_OTHER } /* When len=0, we just calculate the needed length */#define LEN_OR_ZERO (len ? len - pos : 0) static int __initsys_enter_openat_print_fmt(struct syscall_metadata *entry, char *buf, int len){	int pos = 0; 	pos += snprintf(buf + pos, LEN_OR_ZERO,			"\"dfd: 0x%%08lx, filename: 0x%%08lx \\\"%%s\\\", flags: %%s%%s, mode: 0%%03o\",");	pos += snprintf(buf + pos, LEN_OR_ZERO,			" ((unsigned long)(REC->dfd)),");	pos += snprintf(buf + pos, LEN_OR_ZERO,			" ((unsigned long)(REC->filename)),");	pos += snprintf(buf + pos, LEN_OR_ZERO,			" __get_str(__filename_val),");	pos += snprintf(buf + pos, LEN_OR_ZERO,			" (REC->flags & ~3) && !(REC->flags & 3) ? \"O_RDONLY|\" : \"\", ");	pos += snprintf(buf + pos, LEN_OR_ZERO,			" REC->flags ? __print_flags(REC->flags, \"|\", ");	pos += snprintf(buf + pos, LEN_OR_ZERO,			"{ 0x%x, \"O_WRONLY\" }, ", O_WRONLY);	pos += snprintf(buf + pos, LEN_OR_ZERO,			"{ 0x%x, \"O_RDWR\" }, ", O_RDWR);	pos += snprintf(buf + pos, LEN_OR_ZERO,			"{ 0x%x, \"O_CREAT\" }, ", O_CREAT);	pos += snprintf(buf + pos, LEN_OR_ZERO,			"{ 0x%x, \"O_EXCL\" }, ", O_EXCL);	pos += snprintf(buf + pos, LEN_OR_ZERO,			"{ 0x%x, \"O_NOCTTY\" }, ", O_NOCTTY);	pos += snprintf(buf + pos, LEN_OR_ZERO,			"{ 0x%x, \"O_TRUNC\" }, ", O_TRUNC);	pos += snprintf(buf + pos, LEN_OR_ZERO,			"{ 0x%x, \"O_APPEND\" }, ", O_APPEND);	pos += snprintf(buf + pos, LEN_OR_ZERO,			"{ 0x%x, \"O_NONBLOCK\" }, ", O_NONBLOCK);	pos += snprintf(buf + pos, LEN_OR_ZERO,			"{ 0x%x, \"O_DSYNC\" }, ", O_DSYNC);	pos += snprintf(buf + pos, LEN_OR_ZERO,			"{ 0x%x, \"O_DIRECT\" }, ", O_DIRECT);	pos += snprintf(buf + pos, LEN_OR_ZERO,			"{ 0x%x, \"O_LARGEFILE\" }, ", O_LARGEFILE);	pos += snprintf(buf + pos, LEN_OR_ZERO,			"{ 0x%x, \"O_DIRECTORY\" }, ", O_DIRECTORY);	pos += snprintf(buf + pos, LEN_OR_ZERO,			"{ 0x%x, \"O_NOFOLLOW\" }, ", O_NOFOLLOW);	pos += snprintf(buf + pos, LEN_OR_ZERO,			"{ 0x%x, \"O_NOATIME\" }, ", O_NOATIME);	pos += snprintf(buf + pos, LEN_OR_ZERO,			"{ 0x%x, \"O_CLOEXEC\" }) : \"O_RDONLY\", ", O_CLOEXEC); 	pos += snprintf(buf + pos, LEN_OR_ZERO,			" ((unsigned long)(REC->mode))");	return pos;} static int __init__set_enter_print_fmt(struct syscall_metadata *entry, char *buf, int len){	bool is_string = entry->user_arg_is_str;	int i;	int pos = 0; 	switch (entry->syscall_nr) {	case __NR_openat:		return sys_enter_openat_print_fmt(entry, buf, len);	default:		break;	} 	pos += snprintf(buf + pos, LEN_OR_ZERO, "\"");	for (i = 0; i < entry->nb_args; i++) {		if (i)			pos += snprintf(buf + pos, LEN_OR_ZERO, ", ");		pos += snprintf(buf + pos, LEN_OR_ZERO, "%s: 0x%%0%zulx",				entry->args[i], sizeof(unsigned long)); 		if (!(BIT(i) & entry->user_mask))			continue; 		/* Add the format for the user space string or array */		if (entry->user_arg_size < 0 || is_string)			pos += snprintf(buf + pos, LEN_OR_ZERO, " \\\"%%s\\\"");		else			pos += snprintf(buf + pos, LEN_OR_ZERO, " (%%s)");	}	pos += snprintf(buf + pos, LEN_OR_ZERO, "\""); 	for (i = 0; i < entry->nb_args; i++) {		pos += snprintf(buf + pos, LEN_OR_ZERO,				", ((unsigned long)(REC->%s))", entry->args[i]);		if (!(BIT(i) & entry->user_mask))			continue;		/* The user space data for arg has name __<arg>_val */		if (entry->user_arg_size < 0 || is_string) {			pos += snprintf(buf + pos, LEN_OR_ZERO, ", __get_str(__%s_val)",					entry->args[i]);		} else {			pos += snprintf(buf + pos, LEN_OR_ZERO, ", __print_dynamic_array(__%s_val, 1)",					entry->args[i]);		}	} #undef LEN_OR_ZERO 	/* return the length of print_fmt */	return pos;} static int __init set_syscall_print_fmt(struct trace_event_call *call){	char *print_fmt;	int len;	struct syscall_metadata *entry = call->data; 	if (entry->enter_event != call) {		call->print_fmt = "\"0x%lx\", REC->ret";		return 0;

Lines 1–500 of 1683.