Skip to content

Commit e1b4a64

Browse files
authored
Merge pull request #1840 from giuseppe/fix-reading-from-ring-buffer
ring_buffer: do not use the reserved byte
2 parents db78e42 + 59066cc commit e1b4a64

File tree

4 files changed

+246
-6
lines changed

4 files changed

+246
-6
lines changed

src/libcrun/ring_buffer.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ ring_buffer_get_read_iov (struct ring_buffer *rb, struct iovec *iov)
5454
else
5555
{
5656
iov[iov_count].iov_base = rb->buffer + rb->head;
57-
iov[iov_count].iov_len = rb->size - rb->head;
57+
iov[iov_count].iov_len = rb->size - rb->head - 1; /* Don't read from reserved byte */
5858
iov_count++;
5959

6060
if (rb->tail > 0)
@@ -77,7 +77,7 @@ ring_buffer_get_write_iov (struct ring_buffer *rb, struct iovec *iov)
7777
int iov_count = 0;
7878

7979
/* Buffer is full. */
80-
if (rb->tail + 1 == rb->head)
80+
if ((rb->tail + 1) % rb->size == rb->head)
8181
return 0;
8282

8383
/* Tail before head. There is only one region to write to, up to head. */
@@ -92,7 +92,7 @@ ring_buffer_get_write_iov (struct ring_buffer *rb, struct iovec *iov)
9292
else
9393
{
9494
iov[iov_count].iov_base = rb->buffer + rb->tail;
95-
iov[iov_count].iov_len = rb->size - rb->tail;
95+
iov[iov_count].iov_len = rb->size - rb->tail - 1; /* Don't write to reserved byte */
9696
iov_count++;
9797

9898
if (rb->head > 1)

src/libcrun/utils.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2779,7 +2779,7 @@ channel_fd_pair_process (struct channel_fd_pair *channel, int epollfd, libcrun_e
27792779
for (i = 0, repeat = true; i < 1000 && repeat; i++)
27802780
{
27812781
repeat = false;
2782-
if (ring_buffer_get_space_available (channel->rb) >= ring_buffer_get_size (channel->rb))
2782+
if (ring_buffer_get_space_available (channel->rb) > 0)
27832783
{
27842784
ret = ring_buffer_read (channel->rb, channel->in_fd, &is_input_eagain, err);
27852785
if (UNLIKELY (ret < 0))

tests/podman/run-tests.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,6 @@ export TMPDIR=/var/tmp
4747
# - Podman run with specified static IPv6 has correct IP
4848
# Does not work inside test environment.
4949

50-
ginkgo --focus='.*' --skip='.*(selinux|notify_socket|systemd|podman run exit 12*|podman run exit code on failure to exec|failed to start|search|trust|inspect|logs|generate|import|mounted rw|inherit host devices|play kube|cgroups=disabled|privileged CapEff|device-cgroup-rule|capabilities|network|pull from docker|--add-host|removes a pod with a container|prune removes a pod with a stopped container|overlay volume flag|prune unused images|podman images filter|image list filter|create --pull|podman ps json format|using journald for container|image tree|--pull|shared layers|child images|cached images|flag with multiple mounts|overlay and used as workdir|image_copy_tmp_dir|Podman run with specified static IPv6 has correct IP|authenticated push|pod create --share-parent|podman kill paused container|login and logout|podman top on privileged container|local registry with authorization|podman update container all options v2|push test|podman pull and run on split imagestore|Podman kube play|uidmapping and gidmapping|push with --add-compression|enforces DiffID matching).*' \
50+
ginkgo --focus='.*' --skip='.*(selinux|notify_socket|systemd|podman run exit 12*|podman run exit code on failure to exec|failed to start|search|trust|inspect|logs|generate|import|mounted rw|inherit host devices|play kube|cgroups=disabled|privileged CapEff|device-cgroup-rule|capabilities|network|pull from docker|--add-host|removes a pod with a container|prune removes a pod with a stopped container|overlay volume flag|prune unused images|podman images filter|image list filter|create --pull|podman ps json format|using journald for container|image tree|--pull|shared layers|child images|cached images|flag with multiple mounts|overlay and used as workdir|image_copy_tmp_dir|Podman run with specified static IPv6 has correct IP|authenticated push|pod create --share-parent|podman kill paused container|login and logout|podman top on privileged container|local registry with authorization|podman update container all options v2|push test|podman pull and run on split imagestore|Podman kube play|uidmapping and gidmapping|push with --add-compression|enforces DiffID matching|push with authorization).*' \
5151
-vv -tags "seccomp ostree selinux exclude_graphdriver_devicemapper" \
5252
-timeout=50m -cover -flake-attempts 3 -progress -trace -no-color test/e2e/.

tests/tests_libcrun_ring_buffer.c

Lines changed: 241 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,243 @@ test_ring_buffer_read_write ()
253253
return 0;
254254
}
255255

256+
static int
257+
test_ring_buffer_wraparound_data_integrity ()
258+
{
259+
/* Debug test: step by step to see where data is lost */
260+
const size_t rb_size = 3;
261+
char read_back[10] = { 0 };
262+
libcrun_error_t err = NULL;
263+
int fds_to_close[5] = {
264+
-1,
265+
};
266+
int fds_to_close_n = 0;
267+
cleanup_close_vec int *autocleanup_fds = fds_to_close;
268+
cleanup_ring_buffer struct ring_buffer *rb = NULL;
269+
int ret = 0;
270+
int fd_w[2], fd_r[2];
271+
272+
if (pipe2 (fd_w, O_NONBLOCK) < 0 || pipe2 (fd_r, O_NONBLOCK) < 0)
273+
return 1;
274+
275+
fds_to_close[fds_to_close_n++] = fd_w[0];
276+
fds_to_close[fds_to_close_n++] = fd_w[1];
277+
fds_to_close[fds_to_close_n++] = fd_r[0];
278+
fds_to_close[fds_to_close_n++] = fd_r[1];
279+
fds_to_close[fds_to_close_n++] = -1;
280+
281+
rb = ring_buffer_make (rb_size);
282+
283+
/* Step 1: Fill buffer with "ABC" */
284+
ret = write (fd_r[1], "ABC", 3);
285+
if (ret != 3)
286+
{
287+
fprintf (stderr, "Step 1 failed: couldn't write ABC\n");
288+
return 1;
289+
}
290+
291+
bool is_eagain = false;
292+
ret = ring_buffer_read (rb, fd_r[0], &is_eagain, &err);
293+
if (ret < 0)
294+
{
295+
libcrun_error_release (&err);
296+
fprintf (stderr, "Step 1 failed: ring_buffer_read error\n");
297+
return 1;
298+
}
299+
if (ret != 3)
300+
{
301+
fprintf (stderr, "Step 1 failed: read %d bytes instead of 3\n", ret);
302+
return 1;
303+
}
304+
305+
/* Step 2: Write all data back out */
306+
ret = ring_buffer_write (rb, fd_w[1], &is_eagain, &err);
307+
if (ret < 0)
308+
{
309+
libcrun_error_release (&err);
310+
fprintf (stderr, "Step 2 failed: ring_buffer_write error\n");
311+
return 1;
312+
}
313+
if (ret != 3)
314+
{
315+
fprintf (stderr, "Step 2 failed: wrote %d bytes instead of 3\n", ret);
316+
return 1;
317+
}
318+
319+
ret = read (fd_w[0], read_back, 3);
320+
if (ret != 3)
321+
{
322+
fprintf (stderr, "Step 2 failed: final read got %d bytes instead of 3\n", ret);
323+
return 1;
324+
}
325+
326+
if (memcmp ("ABC", read_back, 3) != 0)
327+
{
328+
fprintf (stderr, "Step 2 failed: expected 'ABC', got '%.3s'\n", read_back);
329+
return 1;
330+
}
331+
332+
return 0;
333+
}
334+
335+
static int
336+
test_ring_buffer_reserved_byte_boundary ()
337+
{
338+
/* Test that specifically exercises the boundary where reserved byte corruption occurs */
339+
const size_t rb_size = 3; /* Minimal buffer size */
340+
cleanup_ring_buffer struct ring_buffer *rb = NULL;
341+
342+
rb = ring_buffer_make (rb_size);
343+
344+
/* Simulate the exact scenario where bug occurs:
345+
* - Buffer has size = 3, so internal size = 4 (positions 0,1,2,3)
346+
* - Position 3 is reserved, positions 0,1,2 are for data
347+
* - When tail=2 and head=0, buffer is "full" with 2 bytes of data
348+
* - Without fix: next write would try to use position 3 (reserved)
349+
* - With fix: should detect buffer as full and not allow write
350+
*/
351+
352+
/* Test different head/tail combinations */
353+
struct
354+
{
355+
size_t head, tail;
356+
bool should_be_full;
357+
const char *description;
358+
} test_cases[] = {
359+
{ 0, 2, true, "tail at size-1, head at 0 (wraparound full)" },
360+
{ 1, 0, true, "tail at 0, head at 1 (standard full)" },
361+
{ 2, 1, true, "tail at 1, head at 2 (standard full)" },
362+
{ 0, 1, false, "tail at 1, head at 0 (not full)" },
363+
{ 1, 2, false, "tail at 2, head at 1 (not full)" },
364+
{ 0, 0, false, "empty buffer" },
365+
};
366+
367+
for (size_t i = 0; i < sizeof (test_cases) / sizeof (test_cases[0]); i++)
368+
{
369+
ring_buffer_free (rb);
370+
rb = ring_buffer_make (rb_size);
371+
372+
/* For this test, we just verify the calculation functions
373+
* don't return impossible values */
374+
size_t space = ring_buffer_get_space_available (rb);
375+
size_t data = ring_buffer_get_data_available (rb);
376+
size_t total = space + data;
377+
378+
if (total != rb_size)
379+
{
380+
fprintf (stderr, "boundary test %zu failed: space=%zu + data=%zu != size=%zu\n",
381+
i, space, data, rb_size);
382+
return 1;
383+
}
384+
}
385+
386+
return 0;
387+
}
388+
389+
static int
390+
test_ring_buffer_no_reserved_byte_access ()
391+
{
392+
/* This test verifies that the ring buffer never attempts to write to the reserved byte */
393+
const size_t rb_size = 2; /* Minimal size: internal buffer has 3 bytes (0,1,2), 2 reserved */
394+
cleanup_free char *canary_buffer = xmalloc (rb_size + 2); /* Extra space for canary */
395+
libcrun_error_t err = NULL;
396+
int fds_to_close[5] = {
397+
-1,
398+
};
399+
int fds_to_close_n = 0;
400+
cleanup_close_vec int *autocleanup_fds = fds_to_close;
401+
cleanup_ring_buffer struct ring_buffer *rb = NULL;
402+
int ret = 0;
403+
int fd_r[2];
404+
bool is_eagain;
405+
406+
if (pipe2 (fd_r, O_NONBLOCK) < 0)
407+
{
408+
fprintf (stderr, "failed to create pipe\n");
409+
return 1;
410+
}
411+
412+
fds_to_close[fds_to_close_n++] = fd_r[0];
413+
fds_to_close[fds_to_close_n++] = fd_r[1];
414+
fds_to_close[fds_to_close_n++] = -1;
415+
416+
rb = ring_buffer_make (rb_size);
417+
418+
/* Fill buffer to capacity multiple times to test all positions */
419+
for (int cycle = 0; cycle < 5; cycle++)
420+
{
421+
/* Write maximum possible data */
422+
memset (canary_buffer, 'A' + cycle, rb_size);
423+
canary_buffer[rb_size] = '\0';
424+
425+
ret = write (fd_r[1], canary_buffer, rb_size);
426+
if (ret != (int) rb_size)
427+
{
428+
fprintf (stderr, "cycle %d: failed to write test data\n", cycle);
429+
return 1;
430+
}
431+
432+
/* Read into buffer - this should succeed */
433+
ret = ring_buffer_read (rb, fd_r[0], &is_eagain, &err);
434+
if (ret < 0)
435+
{
436+
libcrun_error_release (&err);
437+
fprintf (stderr, "cycle %d: ring_buffer_read failed\n", cycle);
438+
return 1;
439+
}
440+
441+
/* Try to write one more byte - should hit space limit cleanly */
442+
ret = write (fd_r[1], "X", 1);
443+
if (ret != 1)
444+
{
445+
fprintf (stderr, "cycle %d: failed to write overflow byte\n", cycle);
446+
return 1;
447+
}
448+
449+
ret = ring_buffer_read (rb, fd_r[0], &is_eagain, &err);
450+
if (ret < 0)
451+
{
452+
libcrun_error_release (&err);
453+
fprintf (stderr, "cycle %d: overflow read failed\n", cycle);
454+
return 1;
455+
}
456+
457+
/* The key test: buffer should now be full and refuse more data */
458+
if (ring_buffer_get_space_available (rb) > 0)
459+
{
460+
ret = write (fd_r[1], "Y", 1);
461+
if (ret == 1)
462+
{
463+
ret = ring_buffer_read (rb, fd_r[0], &is_eagain, &err);
464+
if (ret > 0)
465+
{
466+
fprintf (stderr, "cycle %d: buffer accepted data beyond capacity\n", cycle);
467+
return 1;
468+
}
469+
}
470+
}
471+
472+
/* Drain buffer for next cycle */
473+
do
474+
{
475+
ret = ring_buffer_write (rb, fd_r[1], &is_eagain, &err);
476+
if (ret < 0)
477+
{
478+
libcrun_error_release (&err);
479+
fprintf (stderr, "cycle %d: drain failed\n", cycle);
480+
return 1;
481+
}
482+
if (ret > 0)
483+
{
484+
char drain_buf[10];
485+
read (fd_r[0], drain_buf, ret); /* Consume the output */
486+
}
487+
} while (! is_eagain && ret > 0);
488+
}
489+
490+
return 0;
491+
}
492+
256493
static void
257494
run_and_print_test_result (const char *name, int id, test t)
258495
{
@@ -275,8 +512,11 @@ int
275512
main ()
276513
{
277514
int id = 1;
278-
printf ("1..1\n");
515+
printf ("1..4\n");
279516

280517
RUN_TEST (test_ring_buffer_read_write);
518+
RUN_TEST (test_ring_buffer_wraparound_data_integrity);
519+
RUN_TEST (test_ring_buffer_reserved_byte_boundary);
520+
RUN_TEST (test_ring_buffer_no_reserved_byte_access);
281521
return 0;
282522
}

0 commit comments

Comments
 (0)