From e442fc60cf71f159f1138f01ad70d70305ff05d5 Mon Sep 17 00:00:00 2001 From: Alex Markuze Date: Tue, 29 Jul 2025 14:23:05 +0000 Subject: [PATCH] ceph fs debugfs code --- fs/ceph/debugfs.c | 183 +++++++- fs/ceph/super.h | 2 + include/linux/ceph/ceph_san_des.h | 16 +- include/linux/ceph/ceph_san_logger.h | 2 - include/linux/ceph/ceph_san_ser.h | 18 +- net/ceph/ceph_san_des.c | 603 +++++++++++++++++++++------ net/ceph/ceph_san_logger.c | 317 -------------- 7 files changed, 695 insertions(+), 446 deletions(-) diff --git a/fs/ceph/debugfs.c b/fs/ceph/debugfs.c index 7dac6d6eaed00..2ffb291081765 100644 --- a/fs/ceph/debugfs.c +++ b/fs/ceph/debugfs.c @@ -9,8 +9,15 @@ #include #include #include +#include +#include +#include +#include +#include +#include #include +#include #include #include #include @@ -349,6 +356,39 @@ static int mds_sessions_show(struct seq_file *s, void *ptr) return 0; } +/* @buffer: The buffer to store the formatted date and time string. + * @buffer_len: The length of the buffer. + * + * Returns: The number of characters written to the buffer, or a negative error code. + */ +static int jiffies_to_formatted_time(unsigned long jiffies_value, char *buffer, size_t buffer_len) +{ + unsigned long seconds; + unsigned long subsec_jiffies; + unsigned long microseconds; + struct tm tm_time; + time64_t timestamp; + + // Convert jiffies to seconds since boot + seconds = jiffies_value / HZ; + + // Calculate remaining jiffies for subsecond precision + subsec_jiffies = jiffies_value % HZ; + microseconds = (subsec_jiffies * 1000) / HZ; + + // Get current time and calculate absolute timestamp + // Using boottime as reference to convert relative jiffies to absolute time + timestamp = ktime_get_real_seconds() - (jiffies - jiffies_value) / HZ; + + // Convert to broken-down time + time64_to_tm(timestamp, 0, &tm_time); + + // Format the time into the buffer with millisecond precision + return snprintf(buffer, buffer_len, "%04ld-%02d-%02d %02d:%02d:%02d.%03lu", + tm_time.tm_year + 1900, tm_time.tm_mon + 1, tm_time.tm_mday, + tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec, microseconds); +} + static int status_show(struct seq_file *s, void *p) { struct ceph_fs_client *fsc = s->private; @@ -362,6 +402,122 @@ static int status_show(struct seq_file *s, void *p) return 0; } +static int ceph_san_tls_show_internal(struct seq_file *s, void *p) +{ + struct ceph_san_tls_ctx *ctx; + struct ceph_san_log_iter iter; + struct ceph_san_log_entry *entry; + const struct ceph_san_source_info *source; + int total_entries = 0; + int total_contexts = 0; + + /* Lock the logger to safely traverse the contexts list */ + spin_lock(&g_logger.lock); + + list_for_each_entry(ctx, &g_logger.contexts, list) { + /* Initialize iterator for this context's pagefrag */ + ceph_san_log_iter_init(&iter, &ctx->pf); + int pid = ctx->pid; + char *comm = ctx->comm; + int ctx_entries = 0; + + total_contexts++; + + /* Lock the pagefrag before accessing entries */ + spin_lock_bh(&ctx->pf.lock); + + /* Iterate through log entries in this context */ + while ((entry = ceph_san_log_iter_next(&iter)) != NULL) { + char datetime_str[32]; + char reconstructed_msg[256]; + + /* Validate entry before processing */ + if (!entry || !is_valid_kernel_addr(entry)) { + seq_printf(s, "[%d][%s]: Invalid entry pointer %p\n", pid, comm, entry); + break; + } + +#if CEPH_SAN_DEBUG_POISON + if (entry->debug_poison != CEPH_SAN_LOG_ENTRY_POISON) { + seq_printf(s, "[%d][%s]: Corrupted log entry detected\n", pid, comm); + continue; + } +#endif + + /* Calculate absolute timestamp from delta */ + unsigned long abs_jiffies = ctx->base_jiffies + entry->ts_delta; + jiffies_to_formatted_time(abs_jiffies, datetime_str, sizeof(datetime_str)); + + /* Get source information for this ID */ + source = ceph_san_get_source_info(entry->source_id); + if (!source) { + seq_printf(s, "[%d][%s][%s]:[Unknown Source ID %u]: %s\n", + pid, comm, datetime_str, entry->source_id, entry->buffer); + total_entries++; + ctx_entries++; + continue; + } + + seq_printf(s, "[%d][%s][%s]:%s:%s:%u: ", + pid, comm, datetime_str, + source->file, source->func, source->line); + + int ret = ceph_san_log_reconstruct(entry, reconstructed_msg, sizeof(reconstructed_msg)); + if (ret >= 0) + seq_puts(s, reconstructed_msg); + else + seq_printf(s, "", ret); + seq_putc(s, '\n'); + + total_entries++; + ctx_entries++; + } + + /* Unlock the pagefrag after we're done with this context */ + spin_unlock_bh(&ctx->pf.lock); + } + + spin_unlock(&g_logger.lock); + + /* Add summary information */ + seq_printf(s, "\n=== Summary ===\n"); + seq_printf(s, "Total contexts: %d\n", total_contexts); + seq_printf(s, "Total entries: %d\n", total_entries); + + return 0; +} + +static int ceph_san_tls_show(struct seq_file *s, void *p) +{ + return ceph_san_tls_show_internal(s, p); +} + + + +static int ceph_san_contexts_show(struct seq_file *s, void *p) +{ + struct ceph_san_tls_ctx *ctx; + unsigned long flags; + + /* Lock the logger to safely traverse the contexts list */ + spin_lock_irqsave(&g_logger.lock, flags); + seq_puts(s, "Active TLS Contexts:\n"); + seq_puts(s, "PID Command State Context ID Base Jiffies\n"); + seq_puts(s, "------------------------------------------------------------\n"); + list_for_each_entry(ctx, &g_logger.contexts, list) { + char task_state = ctx->task ? task_state_to_char(ctx->task) : 'N'; + + seq_printf(s, "%-8d %-15s [%c] %-12llu %-12lu\n", + ctx->pid, + ctx->comm, + task_state, + ctx->id, + ctx->base_jiffies); + } + spin_unlock_irqrestore(&g_logger.lock, flags); + return 0; +} + DEFINE_SHOW_ATTRIBUTE(mdsmap); DEFINE_SHOW_ATTRIBUTE(mdsc); DEFINE_SHOW_ATTRIBUTE(caps); @@ -371,6 +527,8 @@ DEFINE_SHOW_ATTRIBUTE(metrics_file); DEFINE_SHOW_ATTRIBUTE(metrics_latency); DEFINE_SHOW_ATTRIBUTE(metrics_size); DEFINE_SHOW_ATTRIBUTE(metrics_caps); +DEFINE_SHOW_ATTRIBUTE(ceph_san_tls); +DEFINE_SHOW_ATTRIBUTE(ceph_san_contexts); /* @@ -398,7 +556,7 @@ DEFINE_SIMPLE_ATTRIBUTE(congestion_kb_fops, congestion_kb_get, void ceph_fs_debugfs_cleanup(struct ceph_fs_client *fsc) { - boutc(fsc->client, "begin\n"); + doutc(fsc->client, "begin\n"); debugfs_remove(fsc->debugfs_bdi); debugfs_remove(fsc->debugfs_congestion_kb); debugfs_remove(fsc->debugfs_mdsmap); @@ -406,15 +564,17 @@ void ceph_fs_debugfs_cleanup(struct ceph_fs_client *fsc) debugfs_remove(fsc->debugfs_caps); debugfs_remove(fsc->debugfs_status); debugfs_remove(fsc->debugfs_mdsc); + debugfs_remove(fsc->debugfs_cephsan_tls); + debugfs_remove(fsc->debugfs_cephsan_contexts); debugfs_remove_recursive(fsc->debugfs_metrics_dir); - boutc(fsc->client, "done\n"); + doutc(fsc->client, "done\n"); } void ceph_fs_debugfs_init(struct ceph_fs_client *fsc) { char name[NAME_MAX]; - boutc(fsc->client, "begin\n"); + doutc(fsc->client, "begin\n"); fsc->debugfs_congestion_kb = debugfs_create_file("writeback_congestion_kb", 0600, @@ -459,6 +619,20 @@ void ceph_fs_debugfs_init(struct ceph_fs_client *fsc) fsc, &status_fops); + + fsc->debugfs_cephsan_tls = debugfs_create_file("cephsan_tls", + 0444, + fsc->client->debugfs_dir, + fsc, + &ceph_san_tls_fops); + + /* Add the new contexts-only view */ + fsc->debugfs_cephsan_contexts = debugfs_create_file("cephsan_contexts", + 0444, + fsc->client->debugfs_dir, + fsc, + &ceph_san_contexts_fops); + fsc->debugfs_metrics_dir = debugfs_create_dir("metrics", fsc->client->debugfs_dir); @@ -473,7 +647,6 @@ void ceph_fs_debugfs_init(struct ceph_fs_client *fsc) boutc(fsc->client, "done\n"); } - #else /* CONFIG_DEBUG_FS */ void ceph_fs_debugfs_init(struct ceph_fs_client *fsc) @@ -484,4 +657,4 @@ void ceph_fs_debugfs_cleanup(struct ceph_fs_client *fsc) { } -#endif /* CONFIG_DEBUG_FS */ +#endif /* CONFIG_DEBUG_FS */ diff --git a/fs/ceph/super.h b/fs/ceph/super.h index acef7da8f425b..d980450c6f1a0 100644 --- a/fs/ceph/super.h +++ b/fs/ceph/super.h @@ -153,6 +153,8 @@ struct ceph_fs_client { struct dentry *debugfs_status; struct dentry *debugfs_mds_sessions; struct dentry *debugfs_metrics_dir; + struct dentry *debugfs_cephsan_tls; + struct dentry *debugfs_cephsan_contexts; #endif #ifdef CONFIG_CEPH_FSCACHE diff --git a/include/linux/ceph/ceph_san_des.h b/include/linux/ceph/ceph_san_des.h index eb9d75957f663..79a4b5703adbc 100644 --- a/include/linux/ceph/ceph_san_des.h +++ b/include/linux/ceph/ceph_san_des.h @@ -3,6 +3,9 @@ #include /* For size_t */ +/* Forward declaration */ +struct ceph_san_log_entry; + /** * Reconstructs a formatted string from a buffer containing serialized values. * The function uses the format string to determine the types and number of values @@ -14,9 +17,20 @@ * @param size Size of the buffer in bytes * @param out Buffer to store the reconstructed string * @param out_size Size of the output buffer - * @return Number of bytes written to out buffer, or -1 on error + * @return Number of bytes written to out buffer, or negative error code on failure */ int ceph_san_des_reconstruct(const char *fmt, const void *buffer, size_t nr_args, size_t size, char *out, size_t out_size); +/** + * Reconstructs a formatted string from a log entry. + * This is a wrapper around ceph_san_des_reconstruct that handles log entry parsing. + * + * @param entry Log entry containing serialized data + * @param output Buffer to write the formatted string to + * @param output_size Size of the output buffer + * @return Length of formatted string, or negative error code on failure + */ +int ceph_san_log_reconstruct(const struct ceph_san_log_entry *entry, char *output, size_t output_size); + #endif /* CEPH_SAN_DES_H */ diff --git a/include/linux/ceph/ceph_san_logger.h b/include/linux/ceph/ceph_san_logger.h index 22663a139fc6c..65acf95972c75 100644 --- a/include/linux/ceph/ceph_san_logger.h +++ b/include/linux/ceph/ceph_san_logger.h @@ -115,8 +115,6 @@ void ceph_san_log_iter_init(struct ceph_san_log_iter *iter, struct cephsan_pagef /* Get next log entry, returns NULL when no more entries */ struct ceph_san_log_entry *ceph_san_log_iter_next(struct ceph_san_log_iter *iter); -/* Reconstruct a formatted string from a log entry */ -int ceph_san_log_reconstruct(const struct ceph_san_log_entry *entry, char *output, size_t output_size); /* Initialize the logging system */ int ceph_san_logger_init(void); diff --git a/include/linux/ceph/ceph_san_ser.h b/include/linux/ceph/ceph_san_ser.h index 8f714890cbf6c..365e31b5d2d57 100644 --- a/include/linux/ceph/ceph_san_ser.h +++ b/include/linux/ceph/ceph_san_ser.h @@ -42,7 +42,7 @@ 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) #define ceph_san_narg(...) ___ceph_san_narg(__VA_ARGS__) -#define STR_MAX_SIZE 64 +#define STR_MAX_SIZE 256 #define __sizeof(x) \ (IS_STR(x) ? STR_MAX_SIZE : \ (sizeof(x) < 4) ? 4 : sizeof(x)) @@ -130,7 +130,7 @@ static inline size_t strscpy_n(char *dst, const char *src) } dst[count] = '\0'; - pr_err("strscpy_n: string truncated, exceeded max size %d\n", STR_MAX_SIZE); + pr_warn("strscpy_n: string truncated, exceeded max size %d\n", STR_MAX_SIZE); out: return count + 1; } @@ -145,8 +145,18 @@ static inline ssize_t __strscpy(char *dst, const char *src) static inline void* strscpy_n_update(char *dst, const char *src, const char *file, int line) { ssize_t ret = __strscpy(dst, src); - if (!(unlikely(ret > 0 && ret < STR_MAX_SIZE))) { - panic("strscpy_n_update: ret = %zd at %s:%d :: %s < - %s\n", ret, file, line, dst, src); + if (unlikely(ret <= 0 || ret >= STR_MAX_SIZE)) { + pr_err("strscpy_n_update: string handling error ret=%zd at %s:%d :: dst='%s' src='%s'\n", + ret, file, line, dst, src ? src : "(null)"); + /* Return safely instead of panicking - truncate and continue */ + if (ret >= STR_MAX_SIZE) { + dst[STR_MAX_SIZE - 1] = '\0'; + ret = STR_MAX_SIZE; + } else { + /* Handle null or empty string case */ + dst[0] = '\0'; + ret = 1; + } } return dst + round_up(ret, 4); } diff --git a/net/ceph/ceph_san_des.c b/net/ceph/ceph_san_des.c index b37ae7c849c54..b2aa5f0b180a8 100644 --- a/net/ceph/ceph_san_des.c +++ b/net/ceph/ceph_san_des.c @@ -1,162 +1,531 @@ #include +#include /* For log entry struct and source functions */ #include /* For strchr, strlen */ #include /* For isdigit */ #include /* For size_t */ #include /* For snprintf */ +#include /* For pr_err */ +#include /* For round_up */ -static int parse_format_specifier(const char **fmt, char *spec) { - const char *p = *fmt; - char *s = spec; - /* Skip the '%' */ - if (*p != '%') return -1; - *s++ = *p++; +int ceph_san_des_reconstruct(const char *fmt, const void *buffer, size_t nr_args, + size_t size, char *out, size_t out_size) { + const char *buf_start = (const char *)buffer; + const char *buf_ptr = buf_start; + const char *buf_end = buf_start + size; + const char *fmt_ptr = fmt; + char *out_ptr = out; + size_t remaining = out_size - 1; // Reserve space for null terminator + size_t arg_count = 0; + int ret; - /* Skip flags */ - while (*p && (*p == '-' || *p == '+' || *p == ' ' || *p == '#' || *p == '0')) { - *s++ = *p++; + if (!fmt || !buffer || !out || !out_size) { + pr_err("ceph_san_des_reconstruct: invalid parameters\n"); + return -EINVAL; } - /* Skip field width */ - while (*p && isdigit(*p)) { - *s++ = *p++; - } + *out_ptr = '\0'; - /* Skip precision */ - if (*p == '.') { - *s++ = *p++; - while (*p && isdigit(*p)) { - *s++ = *p++; + // Process the format string + while (*fmt_ptr && remaining > 0) { + if (*fmt_ptr != '%') { + // Copy literal character + *out_ptr++ = *fmt_ptr++; + remaining--; + continue; } - } - /* Get length modifier */ - if (*p == 'h' || *p == 'l' || *p == 'L' || *p == 'j' || *p == 'z' || *p == 't') { - *s++ = *p++; - if ((*p == 'h' || *p == 'l') && *(p-1) == *p) { - *s++ = *p++; + // Skip the '%' + fmt_ptr++; + + // Handle %% + if (*fmt_ptr == '%') { + *out_ptr++ = '%'; + fmt_ptr++; + remaining--; + continue; } - } - /* Get conversion specifier */ - if (*p && strchr("diouxXeEfFgGaAcspn%", *p)) { - *s++ = *p++; - } else { - return -1; - } + // Skip flags (-+#0 space) + while (*fmt_ptr && (*fmt_ptr == '-' || *fmt_ptr == '+' || *fmt_ptr == '#' || + *fmt_ptr == '0' || *fmt_ptr == ' ')) { + fmt_ptr++; + } - *s = '\0'; - *fmt = p; - return 0; -} + // Skip field width (digits or *) + while (*fmt_ptr && (*fmt_ptr >= '0' && *fmt_ptr <= '9')) { + fmt_ptr++; + } + if (*fmt_ptr == '*') { + fmt_ptr++; + } -int ceph_san_des_reconstruct(const char *fmt, const void *buffer, size_t nr_args, - size_t size, char *out, size_t out_size) { - const unsigned char *buf = buffer; - const char *p = fmt; - char spec[32]; - size_t offset = 0; - size_t out_offset = 0; - size_t arg_count = 0; + // Skip precision (.digits or .*) + if (*fmt_ptr == '.') { + fmt_ptr++; + while (*fmt_ptr && (*fmt_ptr >= '0' && *fmt_ptr <= '9')) { + fmt_ptr++; + } + if (*fmt_ptr == '*') { + fmt_ptr++; + } + } - if (!fmt || !buffer || !out || !out_size) { - return -1; - } - //printf("Starting reconstruction with buffer at %p, size %zu, nr_args %zu, out_size %zu\n", - // buffer, size, nr_args, out_size); - while (*p && out_offset < out_size - 1) { - if (*p != '%') { - out[out_offset++] = *p++; - continue; + // Check argument count limit + if (arg_count >= nr_args) { + pr_err("ceph_san_des_reconstruct (%zu): too many format specifiers, expected %zu args. Format: '%.100s'\n", + arg_count, nr_args, fmt); + return -EINVAL; } - if (parse_format_specifier(&p, spec) < 0) { - return -1; + // Parse and handle format specifier + switch (*fmt_ptr) { + case 's': { // String (inline) + const char *str; + size_t str_len; + size_t max_scan_len; + + // Validate we have enough buffer space for at least a null terminator + if (buf_ptr >= buf_end) { + pr_err("ceph_san_des_reconstruct (%zu): buffer overrun at string argument\n", arg_count); + return -EFAULT; + } + + // String is stored inline in buffer + str = buf_ptr; + + // Calculate maximum safe length to scan for null terminator + max_scan_len = buf_end - buf_ptr; + + // Find string length with bounds checking + str_len = strnlen(str, max_scan_len); + if (str_len == max_scan_len && str[str_len - 1] != '\0') { + pr_err("ceph_san_des_reconstruct (%zu): unterminated string in buffer\n", arg_count); + return -EFAULT; + } + + // Advance buffer pointer with proper alignment + buf_ptr += round_up(str_len + 1, 4); + + // Check if buffer advance exceeds entry bounds + if (buf_ptr > buf_end) { + pr_err("ceph_san_des_reconstruct (%zu): string extends beyond buffer bounds\n", arg_count); + return -EFAULT; + } + + // Copy string to output with bounds checking + if (str_len > remaining) + str_len = remaining; + memcpy(out_ptr, str, str_len); + out_ptr += str_len; + remaining -= str_len; + break; } - if (arg_count >= nr_args) { - return -1; + case 'd': case 'i': { // Integer + int val; + + // Check buffer bounds before reading + if (buf_ptr + sizeof(int) > buf_end) { + pr_err("ceph_san_des_reconstruct (%zu): buffer overrun reading int\n", arg_count); + return -EFAULT; + } + + val = *(int *)buf_ptr; + buf_ptr += sizeof(int); + + ret = snprintf(out_ptr, remaining, "%d", val); + if (ret > 0) { + if (ret > remaining) + ret = remaining; + out_ptr += ret; + remaining -= ret; + } + break; } - /* Check buffer overflow */ - if (offset >= size) { - return -1; + case 'u': { // Unsigned integer + unsigned int val; + + // Check buffer bounds before reading + if (buf_ptr + sizeof(unsigned int) > buf_end) { + pr_err("ceph_san_des_reconstruct (%zu): buffer overrun reading unsigned int\n", arg_count); + return -EFAULT; + } + + val = *(unsigned int *)buf_ptr; + buf_ptr += sizeof(unsigned int); + + ret = snprintf(out_ptr, remaining, "%u", val); + if (ret > 0) { + if (ret > remaining) + ret = remaining; + out_ptr += ret; + remaining -= ret; + } + break; } - //printf("Processing specifier '%s' at offset %zu\n", spec, offset); + case 'x': { // Hex integer (lowercase) + unsigned int val; - /* Handle different format specifiers */ - switch (spec[strlen(spec)-1]) { - case 'd': - case 'i': - case 'o': - case 'u': - case 'x': - case 'X': { - long long val; - const void *ptr = buf + offset; - if (strchr(spec, 'l')) { - val = *(const long long*)ptr; - offset += sizeof(long long); - } else { - val = *(const int*)ptr; - offset += sizeof(int); - } - //printf("Read integer value: %lld at address %p (offset %zu)\n", val, ptr, offset); - out_offset += snprintf(out + out_offset, out_size - out_offset, spec, val); - break; + // Check buffer bounds before reading + if (buf_ptr + sizeof(unsigned int) > buf_end) { + pr_err("ceph_san_des_reconstruct (%zu): buffer overrun reading unsigned int\n", arg_count); + return -EFAULT; } - case 'f': - case 'e': - case 'E': - case 'g': - case 'G': - case 'a': - case 'A': { - double val = *(const double*)(buf + offset); - offset += sizeof(double); - //printf("Read double value: %f at offset %zu\n", val, offset - sizeof(double)); - out_offset += snprintf(out + out_offset, out_size - out_offset, spec, val); - break; + val = *(unsigned int *)buf_ptr; + buf_ptr += sizeof(unsigned int); + + ret = snprintf(out_ptr, remaining, "%x", val); + if (ret > 0) { + if (ret > remaining) + ret = remaining; + out_ptr += ret; + remaining -= ret; } + break; + } - case 'c': { - char val = *(const char*)(buf + offset); - offset += sizeof(char); - //printf("Read char value: %c at offset %zu\n", val, offset - sizeof(char)); - out_offset += snprintf(out + out_offset, out_size - out_offset, spec, val); - break; + case 'X': { // Hex integer (uppercase) + unsigned int val; + + // Check buffer bounds before reading + if (buf_ptr + sizeof(unsigned int) > buf_end) { + pr_err("ceph_san_des_reconstruct (%zu): buffer overrun reading unsigned int\n", arg_count); + return -EFAULT; } - case 's': { - const char *val = *(const char**)(buf + offset); - offset += sizeof(const char*); - //printf("Read string pointer: %p at offset %zu\n", val, offset - sizeof(const char*)); - out_offset += snprintf(out + out_offset, out_size - out_offset, spec, val); - break; + val = *(unsigned int *)buf_ptr; + buf_ptr += sizeof(unsigned int); + + ret = snprintf(out_ptr, remaining, "%X", val); + if (ret > 0) { + if (ret > remaining) + ret = remaining; + out_ptr += ret; + remaining -= ret; } + break; + } + + case 'o': { // Octal integer + unsigned int val; - case 'p': { - const void *val = *(const void**)(buf + offset); - offset += sizeof(const void*); - //printf("Read pointer value: %p at offset %zu\n", val, offset - sizeof(const void*)); - out_offset += snprintf(out + out_offset, out_size - out_offset, spec, val); - break; + // Check buffer bounds before reading + if (buf_ptr + sizeof(unsigned int) > buf_end) { + pr_err("ceph_san_des_reconstruct (%zu): buffer overrun reading unsigned int\n", arg_count); + return -EFAULT; } - case '%': { - out[out_offset++] = '%'; - break; + val = *(unsigned int *)buf_ptr; + buf_ptr += sizeof(unsigned int); + + ret = snprintf(out_ptr, remaining, "%o", val); + if (ret > 0) { + if (ret > remaining) + ret = remaining; + out_ptr += ret; + remaining -= ret; + } + break; + } + + case 'p': { // Pointer + void *val; + + // Check buffer bounds before reading + if (buf_ptr + sizeof(void *) > buf_end) { + pr_err("ceph_san_des_reconstruct (%zu): buffer overrun reading pointer\n", arg_count); + return -EFAULT; + } + + val = *(void **)buf_ptr; + buf_ptr += sizeof(void *); + + ret = snprintf(out_ptr, remaining, "%p", val); + if (ret > 0) { + if (ret > remaining) + ret = remaining; + out_ptr += ret; + remaining -= ret; + } + break; + } + + case 'l': { // Long types + fmt_ptr++; // Skip 'l' + if (*fmt_ptr == 'l') { // Long long + fmt_ptr++; // Skip second 'l' + if (*fmt_ptr == 'd' || *fmt_ptr == 'i') { + long long val; + if (buf_ptr + sizeof(long long) > buf_end) { + pr_err("ceph_san_des_reconstruct (%zu): buffer overrun reading long long\n", arg_count); + return -EFAULT; + } + val = *(long long *)buf_ptr; + buf_ptr += sizeof(long long); + ret = snprintf(out_ptr, remaining, "%lld", val); + } else if (*fmt_ptr == 'u') { + unsigned long long val; + if (buf_ptr + sizeof(unsigned long long) > buf_end) { + pr_err("ceph_san_des_reconstruct (%zu): buffer overrun reading unsigned long long\n", arg_count); + return -EFAULT; + } + val = *(unsigned long long *)buf_ptr; + buf_ptr += sizeof(unsigned long long); + ret = snprintf(out_ptr, remaining, "%llu", val); + } else if (*fmt_ptr == 'x') { + unsigned long long val; + if (buf_ptr + sizeof(unsigned long long) > buf_end) { + pr_err("ceph_san_des_reconstruct (%zu): buffer overrun reading unsigned long long\n", arg_count); + return -EFAULT; + } + val = *(unsigned long long *)buf_ptr; + buf_ptr += sizeof(unsigned long long); + ret = snprintf(out_ptr, remaining, "%llx", val); + } else if (*fmt_ptr == 'X') { + unsigned long long val; + if (buf_ptr + sizeof(unsigned long long) > buf_end) { + pr_err("ceph_san_des_reconstruct (%zu): buffer overrun reading unsigned long long\n", arg_count); + return -EFAULT; + } + val = *(unsigned long long *)buf_ptr; + buf_ptr += sizeof(unsigned long long); + ret = snprintf(out_ptr, remaining, "%llX", val); + } else if (*fmt_ptr == 'o') { + unsigned long long val; + if (buf_ptr + sizeof(unsigned long long) > buf_end) { + pr_err("ceph_san_des_reconstruct (%zu): buffer overrun reading unsigned long long\n", arg_count); + return -EFAULT; + } + val = *(unsigned long long *)buf_ptr; + buf_ptr += sizeof(unsigned long long); + ret = snprintf(out_ptr, remaining, "%llo", val); + } else { + pr_err("ceph_san_des_reconstruct: invalid long long format specifier '%%ll%c'\n", *fmt_ptr); + return -EINVAL; + } + } else { // Long + if (*fmt_ptr == 'd' || *fmt_ptr == 'i') { + long val; + if (buf_ptr + sizeof(long) > buf_end) { + pr_err("ceph_san_des_reconstruct (%zu): buffer overrun reading long\n", arg_count); + return -EFAULT; + } + val = *(long *)buf_ptr; + buf_ptr += sizeof(long); + ret = snprintf(out_ptr, remaining, "%ld", val); + } else if (*fmt_ptr == 'u') { + unsigned long val; + if (buf_ptr + sizeof(unsigned long) > buf_end) { + pr_err("ceph_san_des_reconstruct (%zu): buffer overrun reading unsigned long\n", arg_count); + return -EFAULT; + } + val = *(unsigned long *)buf_ptr; + buf_ptr += sizeof(unsigned long); + ret = snprintf(out_ptr, remaining, "%lu", val); + } else if (*fmt_ptr == 'x') { + unsigned long val; + if (buf_ptr + sizeof(unsigned long) > buf_end) { + pr_err("ceph_san_des_reconstruct (%zu): buffer overrun reading unsigned long\n", arg_count); + return -EFAULT; + } + val = *(unsigned long *)buf_ptr; + buf_ptr += sizeof(unsigned long); + ret = snprintf(out_ptr, remaining, "%lx", val); + } else if (*fmt_ptr == 'X') { + unsigned long val; + if (buf_ptr + sizeof(unsigned long) > buf_end) { + pr_err("ceph_san_des_reconstruct (%zu): buffer overrun reading unsigned long\n", arg_count); + return -EFAULT; + } + val = *(unsigned long *)buf_ptr; + buf_ptr += sizeof(unsigned long); + ret = snprintf(out_ptr, remaining, "%lX", val); + } else if (*fmt_ptr == 'o') { + unsigned long val; + if (buf_ptr + sizeof(unsigned long) > buf_end) { + pr_err("ceph_san_des_reconstruct (%zu): buffer overrun reading unsigned long\n", arg_count); + return -EFAULT; + } + val = *(unsigned long *)buf_ptr; + buf_ptr += sizeof(unsigned long); + ret = snprintf(out_ptr, remaining, "%lo", val); + } else { + pr_err("ceph_san_des_reconstruct: invalid long format specifier '%%l%c'\n", *fmt_ptr); + return -EINVAL; + } } - default: - return -1; + if (ret > 0) { + if (ret > remaining) + ret = remaining; + out_ptr += ret; + remaining -= ret; + } + break; + } + + case 'z': { // size_t + fmt_ptr++; // Skip 'z' + if (*fmt_ptr == 'u') { + size_t val; + if (buf_ptr + sizeof(size_t) > buf_end) { + pr_err("ceph_san_des_reconstruct (%zu): buffer overrun reading size_t\n", arg_count); + return -EFAULT; + } + val = *(size_t *)buf_ptr; + buf_ptr += sizeof(size_t); + ret = snprintf(out_ptr, remaining, "%zu", val); + if (ret > 0) { + if (ret > remaining) + ret = remaining; + out_ptr += ret; + remaining -= ret; + } + } else { + pr_err("ceph_san_des_reconstruct: invalid size_t format specifier '%%z%c'\n", *fmt_ptr); + return -EINVAL; + } + break; + } + + default: + // Unknown format specifier + pr_err("ceph_san_des_reconstruct: unknown format specifier '%%%c'\n", *fmt_ptr); + return -EINVAL; } + fmt_ptr++; arg_count++; } - out[out_offset] = '\0'; - return out_offset; + // Remove trailing newline if present + if (out_ptr > out && *(out_ptr - 1) == '\n') { + *(out_ptr - 1) = '\0'; + } else { + *out_ptr = '\0'; + } + + return out_size - remaining - 1; +} + +/** + * ceph_san_log_reconstruct - Reconstruct a formatted string from a log entry + * @entry: Log entry containing serialized data + * @output: Buffer to write the formatted string to + * @output_size: Size of the output buffer + * + * This is a wrapper around ceph_san_des_reconstruct that handles log entry parsing. + * It extracts the format string from the source info and calls the core reconstruction function. + * + * Returns length of formatted string, or negative error code on failure + */ +int ceph_san_log_reconstruct(const struct ceph_san_log_entry *entry, char *output, size_t output_size) +{ + const struct ceph_san_source_info *info; + const char *fmt; + + if (!entry || !output || output_size == 0) { + pr_err("ceph_san_log_reconstruct: invalid parameters\n"); + return -EINVAL; + } + + /* Verify entry is a valid kernel address */ + if (!is_valid_kernel_addr(entry)) { + pr_err("ceph_san_log_reconstruct: invalid entry pointer %p\n", entry); + return -EFAULT; + } + + /* Verify entry buffer is a valid kernel address */ + if (!is_valid_kernel_addr(entry->buffer)) { + pr_err("ceph_san_log_reconstruct: invalid buffer pointer %p for entry %p\n", + entry->buffer, entry); + return -EFAULT; + } + +#if CEPH_SAN_DEBUG_POISON + if (entry->debug_poison != CEPH_SAN_LOG_ENTRY_POISON) { + pr_err("ceph_san_log_reconstruct: corrupted log entry detected\n"); + return -EFAULT; + } +#endif + + // Get format string from source info + info = ceph_san_get_source_info(entry->source_id); + if (!info) { + pr_err("ceph_san_log_reconstruct: source info not found for ID %u\n", entry->source_id); + return -EINVAL; + } + + fmt = info->fmt; + if (!fmt) { + pr_err("ceph_san_log_reconstruct: format string not found in source info for ID %u\n", entry->source_id); + return -EINVAL; + } + + // Count arguments in format string to pass to des_reconstruct + // This must match the parsing logic in ceph_san_des_reconstruct exactly + size_t nr_args = 0; + const char *p = fmt; + while (*p) { + if (*p == '%') { + p++; // Skip '%' + + // Handle %% + if (*p == '%') { + p++; + continue; + } + + // Skip flags (-+#0 space) + while (*p && (*p == '-' || *p == '+' || *p == '#' || + *p == '0' || *p == ' ')) { + p++; + } + + // Skip field width (digits or *) + while (*p && (*p >= '0' && *p <= '9')) { + p++; + } + if (*p == '*') { + p++; + } + + // Skip precision (.digits or .*) + if (*p == '.') { + p++; + while (*p && (*p >= '0' && *p <= '9')) { + p++; + } + if (*p == '*') { + p++; + } + } + + // Check if we have a valid conversion specifier + if (*p == 's' || *p == 'd' || *p == 'i' || *p == 'u' || *p == 'p' || + *p == 'x' || *p == 'X' || *p == 'o' || *p == 'z') { + nr_args++; + } else if (*p == 'l') { + // Handle long types + p++; // Skip 'l' + if (*p == 'l') { + p++; // Skip second 'l' for long long + } + // Now check the conversion specifier + if (*p == 'd' || *p == 'i' || *p == 'u' || *p == 'x' || + *p == 'X' || *p == 'o') { + nr_args++; + } + } + } + if (*p) p++; + } + + // Call the core reconstruction function + return ceph_san_des_reconstruct(fmt, entry->buffer, nr_args, entry->len, output, output_size); } +EXPORT_SYMBOL(ceph_san_log_reconstruct); diff --git a/net/ceph/ceph_san_logger.c b/net/ceph/ceph_san_logger.c index 7cea1eceb4de2..27873a0532474 100644 --- a/net/ceph/ceph_san_logger.c +++ b/net/ceph/ceph_san_logger.c @@ -774,323 +774,6 @@ struct ceph_san_log_entry *ceph_san_log_iter_next(struct ceph_san_log_iter *iter } EXPORT_SYMBOL(ceph_san_log_iter_next); -/** - * ceph_san_log_reconstruct - Reconstruct a formatted string from a log entry - * @entry: Log entry containing serialized data - * @output: Buffer to write the formatted string to - * @output_size: Size of the output buffer - * - * Returns length of formatted string, or negative error code on failure - */ -int ceph_san_log_reconstruct(const struct ceph_san_log_entry *entry, char *output, size_t output_size) -{ - const struct ceph_san_source_info *info; - const char *fmt; - char *in_buffer, *out_ptr; - int ret; - int arg_count = 0; - size_t remaining = output_size - 1; // Reserve space for null terminator - - if (!entry || !output || output_size == 0) { - pr_err("ceph_san_log_reconstruct: invalid parameters\n"); - return -EINVAL; - } - - /* Verify entry is a valid kernel address */ - if (!is_valid_kernel_addr(entry)) { - pr_err("ceph_san_log_reconstruct: invalid entry pointer %p\n", entry); - return -EFAULT; - } - /* Dump entry buffer pointer and validate */ - pr_debug("ceph_san_log_reconstruct: entry buffer pointer %llx (len %u) is %s\n", - (unsigned long long)entry->buffer, entry->len, - is_valid_kernel_addr(entry->buffer) ? "valid" : "invalid"); - /* Verify entry buffer is a valid kernel address */ - if (!is_valid_kernel_addr(entry->buffer)) { - pr_err("ceph_san_log_reconstruct: invalid buffer pointer %p for entry %p\n", - entry->buffer, entry); - return -EFAULT; - } - - // Get format string from source info - info = ceph_san_get_source_info(entry->source_id); - if (!info) { - pr_err("ceph_san_log_reconstruct: source info not found for ID %u\n", entry->source_id); - return -EINVAL; - } - - fmt = info->fmt; - if (!fmt) { - pr_err("ceph_san_log_reconstruct: format string not found in source info for ID %u\n", entry->source_id); - return -EINVAL; - } - - in_buffer = (char *)(entry->buffer); - out_ptr = output; - *out_ptr = '\0'; - - // Process the format string - while (*fmt && remaining > 0) { - if (*fmt != '%') { - // Copy regular characters - *out_ptr++ = *fmt++; - remaining--; - continue; - } - arg_count++; - fmt++; // Skip the '%' - // Skip width specifiers - while (*fmt >= '0' && *fmt <= '9') - fmt++; - - // Handle format specifiers - switch (*fmt) { - case '%': // Literal % - *out_ptr++ = '%'; - remaining--; - break; - case 's': { // String - const char *str; - int type = 0; - // Inline string - str = in_buffer; - if (!is_valid_kernel_addr(str)) { - pr_err("ceph_san_log_reconstruct (%d): invalid inline string pointer %llx\n", arg_count, (unsigned long long)str); - return -EFAULT; - } - in_buffer += round_up(strlen(str) + 1, 4); - size_t len = strlen(str); - if (len > remaining) - len = remaining; - pr_debug("reconstruct: writing %s string '%s' (len=%zu) at out_offset=%ld\n", - type ? "pointer" : "inline", str, strlen(str), in_buffer - entry->buffer); - memcpy(out_ptr, str, len); - out_ptr += len; - remaining -= len; - break; - } - - case 'd': case 'i': { // Integer - int val = *(int *)in_buffer; - in_buffer += sizeof(int); - pr_debug("reconstruct: reading int %d at in_offset=%ld\n", - val, in_buffer - entry->buffer); - ret = snprintf(out_ptr, remaining, "%d", val); - if (ret > 0) { - if (ret > remaining) - ret = remaining; - out_ptr += ret; - remaining -= ret; - } - break; - } - - case 'u': { // Unsigned integer - unsigned int val = *(unsigned int *)in_buffer; - in_buffer += sizeof(unsigned int); - pr_debug("reconstruct: reading int %u at in_offset=%ld\n", - val, in_buffer - entry->buffer); - ret = snprintf(out_ptr, remaining, "%u", val); - if (ret > 0) { - if (ret > remaining) - ret = remaining; - out_ptr += ret; - remaining -= ret; - } - break; - } - - case 'x': case 'X': { // Hex - unsigned int val = *(unsigned int *)in_buffer; - in_buffer += sizeof(unsigned int); - pr_debug("reconstruct: reading int %u at in_offset=%ld\n", - val, in_buffer - entry->buffer); - ret = snprintf(out_ptr, remaining, (*fmt == 'x') ? "%x" : "%X", val); - if (ret > 0) { - if (ret > remaining) - ret = remaining; - out_ptr += ret; - remaining -= ret; - } - break; - } - - case 'p': { // Pointer - void *val = *(void **)in_buffer; - in_buffer += sizeof(void *); - pr_debug("reconstruct: reading pointer %llx at in_offset=%ld\n", - (unsigned long long)val, in_buffer - entry->buffer); - ret = snprintf(out_ptr, remaining, "%p", val); - if (ret > 0) { - if (ret > remaining) - ret = remaining; - out_ptr += ret; - remaining -= ret; - } - break; - } - - case 'o': { // Octal - unsigned int val = *(unsigned int *)in_buffer; - in_buffer += sizeof(unsigned int); - pr_debug("reconstruct: reading int %u (octal: %o) at in_offset=%ld\n", - val, val, in_buffer - entry->buffer); - ret = snprintf(out_ptr, remaining, "%o", val); - if (ret > 0) { - if (ret > remaining) - ret = remaining; - out_ptr += ret; - remaining -= ret; - } - break; - } - - case 'l': { // Long or long long - fmt++; - if (*fmt == 'l') { // Long long - fmt++; - if (*fmt == 'd' || *fmt == 'i') { - long long val = *(long long *)in_buffer; - in_buffer += sizeof(long long); - pr_debug("reconstruct: reading long long %lld at in_offset=%ld\n", - val, in_buffer - entry->buffer); - ret = snprintf(out_ptr, remaining, "%lld", val); - } else if (*fmt == 'u') { - unsigned long long val = *(unsigned long long *)in_buffer; - in_buffer += sizeof(unsigned long long); - pr_debug("reconstruct: reading long long %llu at in_offset=%ld\n", - val, in_buffer - entry->buffer); - ret = snprintf(out_ptr, remaining, "%llu", val); - } else if (*fmt == 'x') { - unsigned long long val = *(unsigned long long *)in_buffer; - in_buffer += sizeof(unsigned long long); - pr_debug("reconstruct: reading long long %llu at in_offset=%ld\n", - val, in_buffer - entry->buffer); - ret = snprintf(out_ptr, remaining, "%llx", val); - } else if (*fmt == 'X') { - unsigned long long val = *(unsigned long long *)in_buffer; - in_buffer += sizeof(unsigned long long); - pr_debug("reconstruct: reading long long %llu at in_offset=%ld\n", - val, in_buffer - entry->buffer); - ret = snprintf(out_ptr, remaining, "%llX", val); - } else if (*fmt == 'o') { - unsigned long long val = *(unsigned long long *)in_buffer; - in_buffer += sizeof(unsigned long long); - pr_debug("reconstruct: reading long long %llu (octal: %llo) at in_offset=%ld\n", - val, val, in_buffer - entry->buffer); - ret = snprintf(out_ptr, remaining, "%llo", val); - } else { - pr_err("ceph_san_log_reconstruct: invalid long long format specifier '%%%c%c%c'\n", 'l', 'l', *fmt); - return -EINVAL; - } - } else { // Long - if (*fmt == 'd' || *fmt == 'i') { - long val = *(long *)in_buffer; - in_buffer += sizeof(long); - pr_debug("reconstruct: reading long %ld at in_offset=%ld\n", - val, in_buffer - entry->buffer); - ret = snprintf(out_ptr, remaining, "%ld", val); - } else if (*fmt == 'u') { - unsigned long val = *(unsigned long *)in_buffer; - in_buffer += sizeof(unsigned long); - pr_debug("reconstruct: reading long %lu at in_offset=%ld\n", - val, in_buffer - entry->buffer); - ret = snprintf(out_ptr, remaining, "%lu", val); - } else if (*fmt == 'x') { - unsigned long val = *(unsigned long *)in_buffer; - in_buffer += sizeof(unsigned long); - pr_debug("reconstruct: reading long %lx at in_offset=%ld\n", - val, in_buffer - entry->buffer); - ret = snprintf(out_ptr, remaining, "%lx", val); - } else if (*fmt == 'X') { - unsigned long val = *(unsigned long *)in_buffer; - in_buffer += sizeof(unsigned long); - pr_debug("reconstruct: reading long %lx at in_offset=%ld\n", - val, in_buffer - entry->buffer); - ret = snprintf(out_ptr, remaining, "%lX", val); - } else if (*fmt == 'o') { - unsigned long val = *(unsigned long *)in_buffer; - in_buffer += sizeof(unsigned long); - pr_debug("reconstruct: reading long %lu (octal: %lo) at in_offset=%ld\n", - val, val, in_buffer - entry->buffer); - ret = snprintf(out_ptr, remaining, "%lo", val); - } else { - pr_err("ceph_san_log_reconstruct: invalid long format specifier '%%l%c'\n", *fmt); - return -EINVAL; - } - } - - if (ret > 0) { - if (ret > remaining) - ret = remaining; - out_ptr += ret; - remaining -= ret; - } - break; - } - - case 'z': { - fmt++; // Look at next character - if (*fmt == 'u' || *fmt == 'd') { - size_t val = *(size_t *)in_buffer; - in_buffer += sizeof(size_t); - pr_debug("reconstruct: reading size_t %zu at in_offset=%ld\n", - val, in_buffer - entry->buffer); - ret = snprintf(out_ptr, remaining, (*fmt == 'u') ? "%zu" : "%zd", val); - if (ret > 0) { - if (ret > remaining) - ret = remaining; - out_ptr += ret; - remaining -= ret; - } - } else if (*fmt == 'x' || *fmt == 'X') { - size_t val = *(size_t *)in_buffer; - in_buffer += sizeof(size_t); - pr_debug("reconstruct: reading size_t %zx at in_offset=%ld\n", - val, in_buffer - entry->buffer); - ret = snprintf(out_ptr, remaining, (*fmt == 'x') ? "%zx" : "%zX", val); - if (ret > 0) { - if (ret > remaining) - ret = remaining; - out_ptr += ret; - remaining -= ret; - } - } else if (*fmt == 'o') { - size_t val = *(size_t *)in_buffer; - in_buffer += sizeof(size_t); - pr_debug("reconstruct: reading size_t %zu (octal: %zo) at in_offset=%ld\n", - val, val, in_buffer - entry->buffer); - ret = snprintf(out_ptr, remaining, "%zo", val); - if (ret > 0) { - if (ret > remaining) - ret = remaining; - out_ptr += ret; - remaining -= ret; - } - } else { - pr_err("ceph_san_log_reconstruct: invalid size_t format specifier '%%z%c'\n", *fmt); - return -EINVAL; - } - break; - } - - default: - // Unknown format specifier - pr_debug("ceph_san_log_reconstruct: unknown format specifier '%%%c' in fmt string\n", *fmt); - return -EINVAL; - } - - fmt++; - } - if (out_ptr > output && *(out_ptr - 1) == '\n') { - *(out_ptr - 1) = '\0'; - } else { - *out_ptr = '\0'; - } - - return output_size - remaining - 1; -} -EXPORT_SYMBOL(ceph_san_log_reconstruct); /** * ceph_san_log_trim - Trim the current context's pagefrag by n bytes -- 2.39.5