Skip to content

Commit 4d164e0

Browse files
jognesspmladek
authored andcommitted
printk: ringbuffer: Fix data block max size check
Currently data_check_size() limits data blocks to a maximum size of the full buffer minus an ID (long integer): max_size <= DATA_SIZE(data_ring) - sizeof(long) However, this is not an appropriate limit due to the nature of wrapping data blocks. For example, if a data block is larger than half the buffer: size = (DATA_SIZE(data_ring) / 2) + 8 and begins exactly in the middle of the buffer, then: - the data block will wrap - the ID will be stored at exactly half of the buffer - the record data begins at the beginning of the buffer - the record data ends 8 bytes _past_ exactly half of the buffer The record overwrites itself, i.e. needs more space than the full buffer! Luckily printk() is not vulnerable to this problem because truncate_msg() limits printk-messages to 1/4 of the ringbuffer. Indeed, by adjusting the printk_ringbuffer KUnit test, which does not use printk() and its truncate_msg() check, it is easy to see that the ringbuffer becomes corrupted for records larger than half the buffer size. The corruption occurs because data_push_tail() expects it will never be requested to push the tail beyond the head. Avoid this problem by adjusting data_check_size() to limit record sizes to half the buffer size. Also add WARN_ON_ONCE() before relevant data_push_tail() calls to validate that there are no such illegal requests. WARN_ON_ONCE() is used, rather than just adding extra checks to data_push_tail() because it is considered a bug to attempt such illegal actions. Link: https://lore.kernel.org/lkml/aMLrGCQSyC8odlFZ@pathway.suse.cz Signed-off-by: John Ogness <john.ogness@linutronix.de> Reviewed-by: Petr Mladek <pmladek@suse.com> Signed-off-by: Petr Mladek <pmladek@suse.com>
1 parent 35a813e commit 4d164e0

1 file changed

Lines changed: 29 additions & 14 deletions

File tree

kernel/printk/printk_ringbuffer.c

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -393,25 +393,21 @@ static unsigned int to_blk_size(unsigned int size)
393393
* Sanity checker for reserve size. The ringbuffer code assumes that a data
394394
* block does not exceed the maximum possible size that could fit within the
395395
* ringbuffer. This function provides that basic size check so that the
396-
* assumption is safe.
396+
* assumption is safe. In particular, it guarantees that data_push_tail() will
397+
* never attempt to push the tail beyond the head.
397398
*/
398399
static bool data_check_size(struct prb_data_ring *data_ring, unsigned int size)
399400
{
400-
struct prb_data_block *db = NULL;
401-
401+
/* Data-less blocks take no space. */
402402
if (size == 0)
403403
return true;
404404

405405
/*
406-
* Ensure the alignment padded size could possibly fit in the data
407-
* array. The largest possible data block must still leave room for
408-
* at least the ID of the next block.
406+
* If data blocks were allowed to be larger than half the data ring
407+
* size, a wrapping data block could require more space than the full
408+
* ringbuffer.
409409
*/
410-
size = to_blk_size(size);
411-
if (size > DATA_SIZE(data_ring) - sizeof(db->id))
412-
return false;
413-
414-
return true;
410+
return to_blk_size(size) <= DATA_SIZE(data_ring) / 2;
415411
}
416412

417413
/* Query the state of a descriptor. */
@@ -1051,8 +1047,17 @@ static char *data_alloc(struct printk_ringbuffer *rb, unsigned int size,
10511047
do {
10521048
next_lpos = get_next_lpos(data_ring, begin_lpos, size);
10531049

1054-
if (!data_push_tail(rb, next_lpos - DATA_SIZE(data_ring))) {
1055-
/* Failed to allocate, specify a data-less block. */
1050+
/*
1051+
* data_check_size() prevents data block allocation that could
1052+
* cause illegal ringbuffer states. But double check that the
1053+
* used space will not be bigger than the ring buffer. Wrapped
1054+
* messages need to reserve more space, see get_next_lpos().
1055+
*
1056+
* Specify a data-less block when the check or the allocation
1057+
* fails.
1058+
*/
1059+
if (WARN_ON_ONCE(next_lpos - begin_lpos > DATA_SIZE(data_ring)) ||
1060+
!data_push_tail(rb, next_lpos - DATA_SIZE(data_ring))) {
10561061
blk_lpos->begin = FAILED_LPOS;
10571062
blk_lpos->next = FAILED_LPOS;
10581063
return NULL;
@@ -1140,8 +1145,18 @@ static char *data_realloc(struct printk_ringbuffer *rb, unsigned int size,
11401145
return &blk->data[0];
11411146
}
11421147

1143-
if (!data_push_tail(rb, next_lpos - DATA_SIZE(data_ring)))
1148+
/*
1149+
* data_check_size() prevents data block reallocation that could
1150+
* cause illegal ringbuffer states. But double check that the
1151+
* new used space will not be bigger than the ring buffer. Wrapped
1152+
* messages need to reserve more space, see get_next_lpos().
1153+
*
1154+
* Specify failure when the check or the allocation fails.
1155+
*/
1156+
if (WARN_ON_ONCE(next_lpos - blk_lpos->begin > DATA_SIZE(data_ring)) ||
1157+
!data_push_tail(rb, next_lpos - DATA_SIZE(data_ring))) {
11441158
return NULL;
1159+
}
11451160

11461161
/* The memory barrier involvement is the same as data_alloc:A. */
11471162
if (!atomic_long_try_cmpxchg(&data_ring->head_lpos, &head_lpos,

0 commit comments

Comments
 (0)