Description of the printk buffer from the headers: ``` The printk log buffer consists of a chain of concatenated variable length records. Every record starts with a record header, containing the overall length of the record. The heads to the first and last entry in the buffer, as well as the sequence numbers of these entries are maintained when messages are stored. If the heads indicate available messages, the length in the header tells the start next message. A length == 0 for the next message indicates a wrap-around to the beginning of the buffer. ``` To be precies, these `heads' are: ``` /* index and sequence number of the first record stored in the buffer */ static u64 log_first_seq; static u32 log_first_idx; /* index and sequence number of the next record to store in the buffer */ static u64 log_next_seq; static u32 log_next_idx; ``` The `seq`, or sequence number, is an unsigned number on 64bits, stricly increasing. Wrapping is not supported, but given the size of that number and the frequency of kernel messages, wrapping should never happen. The `idx`, or index, is the offset in bytes of the message in the buffer. These `heads` are maintained by the writer (`log_store`) which basically, when it has enough space simply: - Write a header and data at log_next_idx - Increase the sequence number and the index: ``` /* insert message */ log_next_idx += msg->len; log_next_seq++; ``` The readers will maintain similar index and sequence numbers, for example: ``` struct devkmsg_user { u64 seq; u32 idx; enum log_flags prev; struct mutex lock; char buf[CONSOLE_EXT_LOG_MAX]; }; ``` At any given, these index and sequence numbers point to the next message that the reader will read (when given the opportunity to). As this is a circular buffer, messages need to be deleted, by updating the first entry in the buffer. This is done by this code: ``` static int log_make_free_space(u32 msg_size) { while (log_first_seq < log_next_seq && !logbuf_has_space(msg_size, false)) { /* drop old messages until we have enough contiguous space */ log_first_idx = log_next(log_first_idx); log_first_seq++; } if (clear_seq < log_first_seq) { clear_seq = log_first_seq; clear_idx = log_first_idx; } /* sequence numbers are equal, so the log buffer is empty */ if (logbuf_has_space(msg_size, log_first_seq == log_next_seq)) return 0; return -ENOMEM; } ``` In reaction, the reader has to verify if the entry he wants to read is still valid or if it has been invalidated (i.e) removed in the mean time. This code is responsible for this check: ``` while (user->seq == log_next_seq) { if (file->f_flags & O_NONBLOCK) { ret = -EAGAIN; raw_spin_unlock_irq(&logbuf_lock); goto out; } raw_spin_unlock_irq(&logbuf_lock); ret = wait_event_interruptible(log_wait, user->seq != log_next_seq); if (ret) goto out; raw_spin_lock_irq(&logbuf_lock); } if (user->seq < log_first_seq) { /* our last seen message is gone, return error and reset */ user->idx = log_first_idx; user->seq = log_first_seq; ret = -EPIPE; raw_spin_unlock_irq(&logbuf_lock); goto out; } ``` Just after this check, the reader will read the memory pointed by user->idx with `msg = log_from_idx(user->idx);`, which simply means: ``` /* get record by index; idx must point to valid msg */ static struct printk_log *log_from_idx(u32 idx) { struct printk_log *msg = (struct printk_log *)(log_buf + idx); /* * A length == 0 record is the end of buffer marker. Wrap around and * read the message at the start of the buffer. */ if (!msg->len) return (struct printk_log *)log_buf; return msg; } ``` Let's now concentrate on buffer wrapping. The writer, if there is no memory avaliable will free memory (see `log_make_free_space`, previously quoted). When freeing that memory, it may wrap and thus has to start putting messages again at the beginning of the buffer. Here is the code responsible: ``` if (log_next_idx + size + sizeof(struct printk_log) > log_buf_len) { /* * This message + an additional empty header does not fit * at the end of the buffer. Add an empty header with len == 0 * to signify a wrap around. */ memset(log_buf + log_next_idx, 0, sizeof(struct printk_log)); log_next_idx = 0; } ``` As you can see, the writer simply writes zeros at the current position and move the index to the beginning. The reader uses the same convention, and if it reads a zeroed header (in fact only the len matters), it will move its index to the beginning (see `log_from_idx`). However, this index change is done without any sequence change and thus that very zeroed memory is not protected by the sequence check! In some very specific circonstances, the reader might try to access a zeroed header which has already been overriden. To be specific, these circonstances are: - The buffer is almost full and the `log_next_seq` is closed to the end, but there is still place for small messages - A reader updates its index and sequence to log_next_* - The next message is too large, resulting in the buffer wrapping-around and a zeroed header to be added at the reader index position - The buffer is completely filled with new messages but without wrapping: + The last message must not wrap around (thus log_first_seq will be equal to the readers's index) + The last message must override the zeroed header (Trigerring the bug) - The reader starts reading again, finding random data instead of the zero 'len' it was supposed to read...