Skip to content

Conversation

@Mes0903
Copy link
Collaborator

@Mes0903 Mes0903 commented Jan 9, 2026

This pull request is derived from #34 and implements VirtIO-GPU 2D and VirtIO-Input devices for semu, enabling graphics and input support without requiring 3D/VirGL dependencies, in conformance with the VirtIO specification v1.3.

VirtIO-GPU

  • Based on VirtIO MMIO transport layer (i.e., Linux platform device)
  • Implements basic 2D rendering support.
    • Resource creation and management
    • 2D transfer (transformation between host and guest memory)
    • Scanout configuration and display flushing
    • Cursor operations (set, move, clear)
  • Provides software-based backend (supports 2D rendering only).
  • Compatible with:
  • Successfully tested with:

VirtIO-Input

  • Implements keyboard device with event forwarding
  • Implements mouse device with absolute positioning support
  • Integrates with window system for event handling

Prerequisites

Note: this list is not yet exhaustive

$ sudo apt install libsdl2-dev libsdl2-2.0-0 libsdl2-image-dev libsdl2-image-2.0-0

Build kernel image and test rootfs image

$ make rootfs.cpio # Download prebuilt minimal rootfs for initrd
$ ./scripts/build-image.sh --all --x11 --external-root --extra-packages

Use --x11 for full setup; omit it for a minimal setup.

Run X11

  1. Launch semu:

    $ make check
  2. Run startx in semu

    $ startx
  3. After X11 shows up, launch the benchmark programs:

    $ glmark2
    $ glxgears

Run DirectFB2

  1. Launch semu:

    $ make check
  2. Kill X11 (Skip if --x11 is not used)

    $ ps | grep 'Xorg'
      <PID> root     /usr/bin/Xorg :0.0 vt01 -s 0 -noreset -allowMouseOpenFail
    $ kill <PID>
  3. Set up environment variables via run.sh:

    $ source ./run.sh
  4. Run DirectFB2 test programs:

    df_andi        df_fire df_neo     df_window
    df_texture     df_video           df_knuckles
    df_input       df_neo             df_window
    df_matrix      df_dok             df_fire
    df_palette     df_cpuload         df_spacedream
    df_layers      df_pss             df_input
    

Run kmscube

  1. Launch semu:

    $ make check
  2. Kill X11 (Skip if --x11 is not used)

    $ ps | grep 'Xorg'
      <PID> root     /usr/bin/Xorg :0.0 vt01 -s 0 -noreset -allowMouseOpenFail
    $ kill <PID>
  3. Run kmscube

    $ kmscube

Run modetest

The simplest method to test VirtIO-GPU.

Unlike #34, this pull request enables the BR2_PACKAGE_LIBDRM_INSTALL_TESTS option by default in the Buildroot

  1. Launch semu:

    $ make check
  2. Run modetest program

    $ modetest -M virtio_gpu -s 37:1024x768

A test image should then show up in the display window.

The 37 is connector ID, you can check it by modetest -M virtio_gpu command. If you encounter a "Invalid argument" error, check your connector ID.

If you encounter a “Permission denied” error, make sure no other process is using the GPU. You can check this with ps | grep -E "Xorg|weston|kmscube|DirectFB" command. Then kill the relevant process(es) and try again.


Summary by cubic

Adds VirtIO-GPU 2D and VirtIO-Input to semu, enabling 2D display and keyboard/mouse without 3D/VirGL. Includes a software window backend and guest build changes to enable DRM and input.

  • New Features

    • VirtIO-GPU 2D over MMIO with software backend: resource create/transfer, scanout, flush, and cursor set/move/clear.
    • Window integration (SDL2) with display and event handling; tested with libdrm modetest, X11 (glxgears, glmark2), kmscube, and DirectFB2.
    • VirtIO-Input keyboard and mouse devices with event forwarding and absolute pointer support.
  • Migration

    • Install SDL2 dev packages; enable locally with ENABLE_SDL=1 (CI sets ENABLE_SDL=0 and disables GPU).
    • Build and boot: make rootfs.cpio; ./scripts/build-image.sh --all [--x11]; make check.
    • Test: modetest -M virtio_gpu -s :1024x768; startx then run glxgears/glmark2; or source target/run.sh and run DirectFB2 demos.

Written for commit 6c8ed02. Summary will update on new commits.

Add support for VirtIO GPU 2D operations and input devices for SEMU:

VirtIO GPU 2D features:
- Software rendering backend (virtio-gpu-sw.c)
- Core GPU device implementation (virtio-gpu.c/h)
- 2D command support: resource creation, transfer, flush
- Cursor operations: set cursor, move cursor, clear cursor
- Scanout management with framebuffer display
- Window system integration (window.h, window-sw.c, window-events.c)

VirtIO Input features:
- Keyboard and mouse device support (virtio-input.c)
- Event code definitions (input-event-codes.h)
- Window event handling integration

This implementation focuses on 2D rendering without 3D/VirGL support,
providing a lightweight graphics solution for the emulator.
Configuration changes:
- Enable VirtIO GPU and input support in Linux kernel config
- Add libdrm support in Buildroot (including test utilities)
- Add X11 build configuration with Meson cross-file
- Update Makefile to build virtio-gpu and window components

These changes enable the build system to compile and integrate
the virtio-gpu 2D implementation with DirectFB2 support.
Changes:
- Add target/run.sh for running DirectFB2 examples
- Update build-image.sh to support DirectFB2 as extra package
- Update rootfs_ext4.sh for proper rootfs generation
- Disable SDL support in GitHub Actions CI

DirectFB2 is built as an extra package to provide graphics support for
the virtio-gpu 2D implementation.
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

17 issues found across 24 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="scripts/rootfs_ext4.sh">

<violation number="1" location="scripts/rootfs_ext4.sh:14">
P2: This `cp` command will fail if the `extra_packages` directory doesn't exist. The directory is not present in the repository and there's no guard checking for its existence. Consider adding a conditional check to only copy when the directory exists.</violation>
</file>

<file name="virtio-input.c">

<violation number="1" location="virtio-input.c:598">
P2: Halfword reads (RV_MEM_LH, RV_MEM_LHU) are passing size=1 instead of size=2. While the FIXME comment suggests per-byte access, this could cause incorrect behavior if the guest issues actual halfword reads. Consider separating the cases or documenting why size=1 is correct for all access widths.</violation>
</file>

<file name="configs/riscv-cross-file">

<violation number="1" location="configs/riscv-cross-file:16">
P2: Invalid `cpu_family` value: `'riscv'` is not a valid Meson cpu_family. According to Meson's reference tables, valid values for RISC-V are `'riscv32'` or `'riscv64'`. Since this targets 32-bit RISC-V, use `'riscv32'`.</violation>
</file>

<file name="window-sw.c">

<violation number="1" location="window-sw.c:96">
P2: Missing NULL check: `SDL_CreateRGBSurfaceWithFormatFrom` can return NULL on allocation failure. The returned `surface` is used directly in `SDL_CreateTextureFromSurface` without validation, which could cause undefined behavior.</violation>

<violation number="2" location="window-sw.c:212">
P1: Race condition: `cursor_clear_sw` modifies shared state and signals condition variable without holding the mutex. This can cause data races with the window thread which reads `cursor_rect` and `cursor` while holding the lock. Add `window_lock_mutex(scanout_id)` before modifying state and `window_unlock_mutex(scanout_id)` after signaling.</violation>

<violation number="3" location="window-sw.c:246">
P2: Race condition: cursor position is updated before acquiring the mutex. The `cursor_rect.x` and `cursor_rect.y` assignments should be inside the critical section to prevent data races with the window thread.</violation>
</file>

<file name="virtio-gpu.h">

<violation number="1" location="virtio-gpu.h:143">
P1: Pointer `uint8_t *capset_data` inside a PACKED structure is problematic. PACKED structures are used for wire format serialization, and pointers (which contain host addresses and vary in size between architectures) are not suitable. Consider using a flexible array member `uint8_t capset_data[];` instead.</violation>

<violation number="2" location="virtio-gpu.h:299">
P2: Typo in field name: `trasfer_to_host_2d` should be `transfer_to_host_2d` (missing 'n'). This inconsistency with the corresponding enum `VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D` could lead to confusion and bugs.</violation>

<violation number="3" location="virtio-gpu.h:332">
P2: Typo in function name: `vgpu_destory_resource_2d` should be `vgpu_destroy_resource_2d` (misspelled 'destroy' as 'destory').</violation>
</file>

<file name="virtio-gpu-sw.c">

<violation number="1" location="virtio-gpu-sw.c:26">
P1: Memory leak: `res_2d->iovec` is allocated but not freed when returning early due to corrupted page address. Add `free(res_2d->iovec); res_2d->iovec = NULL;` before the return statement.</violation>

<violation number="2" location="virtio-gpu-sw.c:82">
P2: Incorrect buffer allocation formula mixes pixels and bytes. `request->width` is in pixels while `res_2d->stride` is in bytes (4096). The formula should likely be `res_2d->stride * request->height` since stride already represents bytes per row.</violation>
</file>

<file name="common.h">

<violation number="1" location="common.h:25">
P1: Using `1 << bit` where `bit` can be >= 32 causes undefined behavior. The literal `1` is of type `int` (32-bit), but `bit` is `unsigned long` and can exceed 31 on 64-bit systems (since `BITS_PER_LONG` would be 64). Use `1UL << bit` to ensure the shift operates on an `unsigned long`.</violation>
</file>

<file name="virtio-gpu.c">

<violation number="1" location="virtio-gpu.c:78">
P0: Buffer offset calculated twice: `dest` already includes `done` offset (line 76), but `memcpy` adds `done` again. This writes data to the wrong location (`buf + 2*done` instead of `buf + done`).</violation>

<violation number="2" location="virtio-gpu.c:273">
P1: Logical AND `&&` used instead of bitwise AND `&`. This results in `edid[9]` being set to 1 (if vendor_id is non-zero) instead of the lower 8 bits of vendor_id, producing an incorrect EDID vendor identifier.</violation>
</file>

<file name="scripts/build-image.sh">

<violation number="1" location="scripts/build-image.sh:180">
P1: Missing directory existence check before `git clone`. If the script is run twice, this will fail because the `DirectFB2` directory already exists. This is inconsistent with the patterns used in `do_buildroot` and `do_linux` which check `if [ ! -d directory ]` before cloning.</violation>

<violation number="2" location="scripts/build-image.sh:190">
P1: Missing directory existence check before `git clone`. Same issue as above - running the script twice will fail because the `DirectFB-examples` directory already exists.</violation>
</file>

<file name="window-events.c">

<violation number="1" location="window-events.c:86">
P1: Copy-paste error: `SDLK_F7` is mapped twice while `SDLK_F8` is missing. The F8 key won't work.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

cubic-dev-ai[bot]

This comment was marked as resolved.

Copy link
Collaborator

@shengwen-tw shengwen-tw Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is copied directly from the Linux kernel source.
Define the key codes required by semu and retain the reference to the original source to avoid license violations.

[host_machine]
system = 'linux'
cpu_family = 'riscv'
cpu_family = 'riscv32'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reference to justify this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are only riscv32 and riscv64 in the Meson reference table for CPU families

https://mesonbuild.com/Reference-tables.html#cpu-families

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, thanks for the clarification.


struct key_map_entry key_map[] = {
/* Mouse */
DEF_KEY_MAP(SDL_BUTTON_LEFT, BTN_LEFT),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not cover all key code, we can create a new GitHub issue once the pull request is merged.

res_2d->stride = STRIDE_SIZE;
res_2d->image = malloc(bytes_per_pixel * (request->width + res_2d->stride) *
request->height);
res_2d->image = malloc(res_2d->stride * request->height);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why this change is made.
Could you compare with this reference? (line 511)
psun3x/acrn-hypervisor@edc137c

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The call graph in the reference you gave will call

pixman_image_create_bits(r2d->format, r2d->width, r2d->height, NULL, 0);

This will get into create_bits_image_internal with rowstride_bytes set to 0. Then it call create_bits further, which is the place that will calculate the stride.

In create_bits, the main process for the calculation of stride is:

  1. stride = width * bpp;: Calculate the total bit.
  2. stride += 0x1f;: Alignment.
  3. stride >>= 5;: Equivlaent to devided by 32, to calculate how many 32-bit it need.
  4. stride *= sizeof(uint32_t);: Transform to byte.

For example, take width as 100, if the format is VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM, which will be transformed to PIXMAN_x8r8g8b8, then the bit per pixel (bpp) is 32, thus the stride is ((100 * 32 + 31) / 32) * 4, which is 400 (byte).

After it get the stride, it will just calculate the size of buffer via height * stride directly and malloc(buf_size).

Thus, I think the point is that the size we need to malloc is height * stride, but the original implementation mixes pixels and bytes (request->width + res_2d->stride). The correct formula is (bytes_per_pixel * request->width) * request->height, where the first part is res_2d->stride.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, as @visitorckw mentioned below, the res_2d->stride should be calculated as width * bpp, but I'm not sure if I need to do the alignment here.

I briefly looked through the kernel code. For dumb buffers, it's simply width * bpp without alignment.

https://elixir.bootlin.com/linux/v6.12.64/source/drivers/gpu/drm/virtio/virtgpu_gem.c#L74

Copy link
Collaborator

@shengwen-tw shengwen-tw Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original implementation of my virtio-gpu used pixman. When I attempted to remove the pixman dependency, I introduced a hard-coded STRIDE_SIZE of 4096 as @visitorckw pointed out. After reviewing the VirtIO GPU spec. again, I think this may be incorrect. shengwen-tw@1120d55

In virtio-gpu 2D mode (VIRTIO_GPU_CMD_RESOURCE_CREATE_2D, VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D, and VIRTIO_GPU_CMD_SET_SCANOUT), the protocol does not define an explicit stride field. This suggests that the expected memory layout might be just tightly packed, i.e., each row is exactly width * bytes_per_pixel with no additional per-row padding. If so, explicit stride handling may be unnecessary or even incorrect.

@Mes0903 :
Could you help run some experiments to confirm whether we can remove the stride handling entirely? For example, by checking the size of the received buffer and testing what happens when STRIDE_SIZE is set to 0.

function copy_buildroot_config
{
local buildroot_config="configs/buildroot.config"
local x11_config="configs/x11.config"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jserv:
I prefer to keep the X11 build configuration optional, as we will need it later to test and verify VirtIO GPU 3D mode.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer to keep the X11 build configuration optional, as we will need it later to test and verify VirtIO GPU 3D mode.

X11 specific configurations should be excluded for this pull request, keeping it simple.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Mes0903
Let’s separate the X11 build option from this pull request and move it into a new commit, in case we need to use it later.

Copy link
Collaborator

@jserv jserv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add headless tests in CI/CD.

virtio-input.c Outdated
vinput->ram[queue->QueueUsed] &= MASK(16);
/* Update the used ring pointer (virtq_used.idx) */
/* TODO: Check if the or-ing is valid or not */
// vinput->ram[queue->QueueUsed] |= ((uint32_t) new_used) << 16;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Mes0903
Could you check the correct implementation according to the VirtIO spec.?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the current implementation already following the spec, but the MASK(16) is for lower 16 bits, which means keep lower 16 bits (flags) and clean higher 16 bits (idx). I think the original implementation is contrary to the comment.

I changed to the following implementation for readability for now:

/* Update used ring header */
uint16_t *used_hdr = (uint16_t *) &vinput->ram[queue->QueueUsed];
used_hdr[0] = 0;        /* virtq_used.flags */
used_hdr[1] = new_used; /* virtq_used.idx */

Also, the spec said

A device MUST NOT write to any descriptor table entry.

So I delete the desc[3] = 0 part and add some simple check.

break;
case RV_MEM_SB:
case RV_MEM_SH:
/* FIXME: virtio-input driver need to access device config register per
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please refactor the code here.

Copy link
Collaborator

@visitorckw visitorckw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Patches 4 and 5 appear to be fixups for earlier commits. This violates commit atomicity and breaks bisectability. Please use git rebase -i to squash them into the relevant patches.

virtio-gpu-sw.c Outdated
#include "virtio-gpu.h"
#include "window.h"

#define STRIDE_SIZE 4096
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How was this value derived?

Shouldn't this be calculated dynamically based on width * bytes_per_pixel (or the stride provided by the guest) to support arbitrary resolutions and avoid potential display corruption?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're right here, thank you.

@shengwen-tw
Copy link
Collaborator

Patches 4 and 5 appear to be fixups for earlier commits. This violates commit atomicity and breaks bisectability. Please use git rebase -i to squash them into the relevant patches.

@Mes0903
Please rebase the commits once the following issue is addressed:
https://github.com/sysprog21/semu/pull/121/files/d56eae31d498ba1c7b84e3bda2ed4b6c9671f439..a641357e4101ab10111311fdb3ebb2a86d7fdeb5#diff-ff10d79192b7c7bd8c0fd33ee7d45867c5f30eb1866a0a22f8b7c2b4e159312eR82

@ChinYikMing
Copy link
Collaborator

@Mes0903 , could you please upload the patched rootfs.cpio to this PR thread for quick testing? Thanks.

@sysprog21 sysprog21 deleted a comment from cubic-dev-ai bot Jan 11, 2026
@sysprog21 sysprog21 deleted a comment from cubic-dev-ai bot Jan 11, 2026
/* supplied by environment */
uint32_t *ram;
/* implementation-specific */
int id; // FIXME
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More comment for this?

#if SEMU_HAS(VIRTIOGPU)
emu->vgpu.ram = emu->ram;
virtio_gpu_init(&(emu->vgpu));
virtio_gpu_add_scanout(&(emu->vgpu), 1024, 768);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you reuse SCREEN_WIDTH and SCREEN_HEIGHT?

bits_per_pixel = 32;
break;
case VIRTIO_GPU_FORMAT_R8G8B8X8_UNORM:
bits_per_pixel = 32;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cases here and above are all do the same thing: bits_per_pixel = 32; break;, can you merge them?

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

11 issues found across 24 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="target/run.sh">

<violation number="1" location="target/run.sh:3">
P2: Avoid introducing an empty LD_LIBRARY_PATH entry when the variable was previously unset—an empty entry makes the dynamic linker search the current directory first, which is a security risk.</violation>
</file>

<file name="scripts/build-image.sh">

<violation number="1" location="scripts/build-image.sh:175">
P2: Inconsistent PATH handling: appends buildroot toolchain to PATH instead of prepending. Other functions (`do_linux`, `do_extra_packages`) correctly prepend to ensure the cross-compiler is found first. This could cause the wrong toolchain to be used if the system has conflicting tools.</violation>
</file>

<file name="virtio-gpu.h">

<violation number="1" location="virtio-gpu.h:7">
P2: Duplicate macro definition. `VIRTIO_GPU_MAX_SCANOUTS` is already defined in `virtio.h` which is included by this file. Remove this duplicate definition to avoid potential maintenance issues if the values diverge.</violation>
</file>

<file name="window-sw.c">

<violation number="1" location="window-sw.c:245">
P1: Missing NULL check after `vgpu_get_resource_2d`. If `res_id` is invalid, the function returns NULL and `resource->format` on line 249 will cause a null pointer dereference. Other call sites in virtio-gpu-sw.c always check for NULL.</violation>

<violation number="2" location="window-sw.c:271">
P1: Missing NULL check after `malloc`. If memory allocation fails, `memcpy` on line 267 will dereference NULL pointer.</violation>
</file>

<file name="scripts/rootfs_ext4.sh">

<violation number="1" location="scripts/rootfs_ext4.sh:16">
P2: Copying extra packages with `cp -r` drops ownership/permission data and dereferences symlinks; use `cp -a` to preserve metadata so the rootfs image contains valid files.</violation>
</file>

<file name="virtio-input.c">

<violation number="1" location="virtio-input.c:666">
P1: Missing bounds check on `vinput_dev_cnt` before using as array index. If `virtio_input_init` is called more than `VINPUT_DEV_CNT` (2) times, this causes an out-of-bounds write to `vinput_dev[]`.</violation>
</file>

<file name="window-events.c">

<violation number="1" location="window-events.c:138">
P1: Busy-waiting loop will consume 100% CPU when no events are pending. Consider using `SDL_WaitEvent` instead of `SDL_PollEvent`, or add `SDL_Delay(1)` inside the loop when no event is received.</violation>

<violation number="2" location="window-events.c:145">
P2: Unchecked return value of `sdl_key_to_linux_key`. When the key is not found, -1 is passed to `virtio_input_update_key`, which will be interpreted as 0xFFFFFFFF (invalid key code). Add a check before calling the virtio function.</violation>
</file>

<file name="virtio-gpu-sw.c">

<violation number="1" location="virtio-gpu-sw.c:32">
P1: Memory leak: when `malloc(res_2d->image)` fails, the previously allocated `res_2d` structure is not freed and not removed from the resource list. Call `vgpu_destroy_resource_2d(request->resource_id)` before returning to properly clean up.</violation>

<violation number="2" location="virtio-gpu-sw.c:209">
P1: Potential NULL pointer dereference: `res_2d->iovec` may be NULL if `attach_backing` hasn't been called yet for this resource. Add a NULL check before accessing `res_2d->iovec[0].iov_base`.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

memcpy(&display->cursor, resource, sizeof(struct vgpu_resource_2d));
size_t pixels_size = sizeof(uint32_t) * resource->width * resource->height;
free(display->cursor_img);
display->cursor_img = malloc(pixels_size);
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Missing NULL check after malloc. If memory allocation fails, memcpy on line 267 will dereference NULL pointer.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At window-sw.c, line 271:

<comment>Missing NULL check after `malloc`. If memory allocation fails, `memcpy` on line 267 will dereference NULL pointer.</comment>

<file context>
@@ -0,0 +1,357 @@
+    memcpy(&display->cursor, resource, sizeof(struct vgpu_resource_2d));
+    size_t pixels_size = sizeof(uint32_t) * resource->width * resource->height;
+    free(display->cursor_img);
+    display->cursor_img = malloc(pixels_size);
+    display->cursor.image = display->cursor_img;
+    memcpy(display->cursor_img, resource->image, pixels_size);
</file context>
Suggested change
display->cursor_img = malloc(pixels_size);
display->cursor_img = malloc(pixels_size);
if (!display->cursor_img) {
fprintf(stderr, "%s(): failed to allocate cursor image\n", __func__);
window_unlock_mutex(scanout_id);
return -1;
}
Fix with Cubic


static int cursor_update_sw(int scanout_id, int res_id, int x, int y)
{
struct vgpu_resource_2d *resource = vgpu_get_resource_2d(res_id);
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Missing NULL check after vgpu_get_resource_2d. If res_id is invalid, the function returns NULL and resource->format on line 249 will cause a null pointer dereference. Other call sites in virtio-gpu-sw.c always check for NULL.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At window-sw.c, line 245:

<comment>Missing NULL check after `vgpu_get_resource_2d`. If `res_id` is invalid, the function returns NULL and `resource->format` on line 249 will cause a null pointer dereference. Other call sites in virtio-gpu-sw.c always check for NULL.</comment>

<file context>
@@ -0,0 +1,357 @@
+
+static int cursor_update_sw(int scanout_id, int res_id, int x, int y)
+{
+    struct vgpu_resource_2d *resource = vgpu_get_resource_2d(res_id);
+
+    /* Convert virtio-gpu resource format to SDL format */
</file context>
Suggested change
struct vgpu_resource_2d *resource = vgpu_get_resource_2d(res_id);
struct vgpu_resource_2d *resource = vgpu_get_resource_2d(res_id);
if (!resource) {
fprintf(stderr, "%s(): invalid resource id %d\n", __func__, res_id);
return -1;
}
Fix with Cubic


void virtio_input_init(virtio_input_state_t *vinput)
{
vinput->id = vinput_dev_cnt;
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Missing bounds check on vinput_dev_cnt before using as array index. If virtio_input_init is called more than VINPUT_DEV_CNT (2) times, this causes an out-of-bounds write to vinput_dev[].

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At virtio-input.c, line 666:

<comment>Missing bounds check on `vinput_dev_cnt` before using as array index. If `virtio_input_init` is called more than `VINPUT_DEV_CNT` (2) times, this causes an out-of-bounds write to `vinput_dev[]`.</comment>

<file context>
@@ -0,0 +1,670 @@
+
+void virtio_input_init(virtio_input_state_t *vinput)
+{
+    vinput->id = vinput_dev_cnt;
+    vinput_dev_cnt++;
+
</file context>
Fix with Cubic


while (1) {
SDL_Event e;
if (SDL_PollEvent(&e)) {
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Busy-waiting loop will consume 100% CPU when no events are pending. Consider using SDL_WaitEvent instead of SDL_PollEvent, or add SDL_Delay(1) inside the loop when no event is received.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At window-events.c, line 138:

<comment>Busy-waiting loop will consume 100% CPU when no events are pending. Consider using `SDL_WaitEvent` instead of `SDL_PollEvent`, or add `SDL_Delay(1)` inside the loop when no event is received.</comment>

<file context>
@@ -0,0 +1,170 @@
+
+    while (1) {
+        SDL_Event e;
+        if (SDL_PollEvent(&e)) {
+            switch (e.type) {
+            case SDL_QUIT: {
</file context>
Fix with Cubic

vgpu_create_resource_2d(request->resource_id);
if (!res_2d) {
fprintf(stderr, "%s(): failed to allocate new resource\n", __func__);
virtio_gpu_set_fail(vgpu);
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Memory leak: when malloc(res_2d->image) fails, the previously allocated res_2d structure is not freed and not removed from the resource list. Call vgpu_destroy_resource_2d(request->resource_id) before returning to properly clean up.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At virtio-gpu-sw.c, line 32:

<comment>Memory leak: when `malloc(res_2d->image)` fails, the previously allocated `res_2d` structure is not freed and not removed from the resource list. Call `vgpu_destroy_resource_2d(request->resource_id)` before returning to properly clean up.</comment>

<file context>
@@ -0,0 +1,392 @@
+        vgpu_create_resource_2d(request->resource_id);
+    if (!res_2d) {
+        fprintf(stderr, "%s(): failed to allocate new resource\n", __func__);
+        virtio_gpu_set_fail(vgpu);
+        return;
+    }
</file context>
Fix with Cubic

@@ -0,0 +1,4 @@
#!/usr/bin/bash

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Avoid introducing an empty LD_LIBRARY_PATH entry when the variable was previously unset—an empty entry makes the dynamic linker search the current directory first, which is a security risk.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At target/run.sh, line 3:

<comment>Avoid introducing an empty LD_LIBRARY_PATH entry when the variable was previously unset—an empty entry makes the dynamic linker search the current directory first, which is a security risk.</comment>

<file context>
@@ -0,0 +1,4 @@
+#!/usr/bin/bash
+
+export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
+export PATH=$PATH:/usr/local/bin/
</file context>
Suggested change
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}/usr/local/lib
Fix with Cubic


function do_directfb
{
export PATH=$PATH:$PWD/buildroot/output/host/bin
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Inconsistent PATH handling: appends buildroot toolchain to PATH instead of prepending. Other functions (do_linux, do_extra_packages) correctly prepend to ensure the cross-compiler is found first. This could cause the wrong toolchain to be used if the system has conflicting tools.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/build-image.sh, line 175:

<comment>Inconsistent PATH handling: appends buildroot toolchain to PATH instead of prepending. Other functions (`do_linux`, `do_extra_packages`) correctly prepend to ensure the cross-compiler is found first. This could cause the wrong toolchain to be used if the system has conflicting tools.</comment>

<file context>
@@ -133,11 +170,72 @@ while [[ $# -gt 0 ]]; do
 
+function do_directfb
+{
+    export PATH=$PATH:$PWD/buildroot/output/host/bin
+    export BUILDROOT_OUT=$PWD/buildroot/output/
+    mkdir -p directfb
</file context>
Suggested change
export PATH=$PATH:$PWD/buildroot/output/host/bin
export PATH="$PWD/buildroot/output/host/bin:$PATH"
Fix with Cubic


#include "virtio.h"

#define VIRTIO_GPU_MAX_SCANOUTS 16
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Duplicate macro definition. VIRTIO_GPU_MAX_SCANOUTS is already defined in virtio.h which is included by this file. Remove this duplicate definition to avoid potential maintenance issues if the values diverge.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At virtio-gpu.h, line 7:

<comment>Duplicate macro definition. `VIRTIO_GPU_MAX_SCANOUTS` is already defined in `virtio.h` which is included by this file. Remove this duplicate definition to avoid potential maintenance issues if the values diverge.</comment>

<file context>
@@ -0,0 +1,351 @@
+
+#include "virtio.h"
+
+#define VIRTIO_GPU_MAX_SCANOUTS 16
+#define VGPU_CMD_UNDEF virtio_gpu_cmd_undefined_handler
+
</file context>
Fix with Cubic


if [ -d extra_packages ] && [ -n "$(ls -A extra_packages 2>/dev/null)" ]; then
echo "[*] Copying extra packages..."
cp -r extra_packages/. $DIR/
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Copying extra packages with cp -r drops ownership/permission data and dereferences symlinks; use cp -a to preserve metadata so the rootfs image contains valid files.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/rootfs_ext4.sh, line 16:

<comment>Copying extra packages with `cp -r` drops ownership/permission data and dereferences symlinks; use `cp -a` to preserve metadata so the rootfs image contains valid files.</comment>

<file context>
@@ -11,6 +11,13 @@ echo "[*] Remove old rootfs directory..."
 
+if [ -d extra_packages ] && [ -n "$(ls -A extra_packages 2>/dev/null)" ]; then
+    echo "[*] Copying extra packages..."
+    cp -r extra_packages/. $DIR/
+else
+    echo "[*] No extra_packages directory or directory is empty, skipping..."
</file context>
Suggested change
cp -r extra_packages/. $DIR/
cp -a extra_packages/. $DIR/
Fix with Cubic

}
case SDL_KEYDOWN: {
linux_key = sdl_key_to_linux_key(e.key.keysym.sym);
virtio_input_update_key(linux_key, 1);
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Unchecked return value of sdl_key_to_linux_key. When the key is not found, -1 is passed to virtio_input_update_key, which will be interpreted as 0xFFFFFFFF (invalid key code). Add a check before calling the virtio function.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At window-events.c, line 145:

<comment>Unchecked return value of `sdl_key_to_linux_key`. When the key is not found, -1 is passed to `virtio_input_update_key`, which will be interpreted as 0xFFFFFFFF (invalid key code). Add a check before calling the virtio function.</comment>

<file context>
@@ -0,0 +1,170 @@
+            }
+            case SDL_KEYDOWN: {
+                linux_key = sdl_key_to_linux_key(e.key.keysym.sym);
+                virtio_input_update_key(linux_key, 1);
+                break;
+            }
</file context>
Suggested change
virtio_input_update_key(linux_key, 1);
if (linux_key >= 0)
virtio_input_update_key(linux_key, 1);
Fix with Cubic

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

17 issues found across 24 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="target/run.sh">

<violation number="1" location="target/run.sh:3">
P2: Prevent LD_LIBRARY_PATH from acquiring a leading empty entry (which adds the current directory to the search path) when the variable is initially unset.</violation>

<violation number="2" location="target/run.sh:4">
P2: Guard against introducing an empty PATH entry so the current working directory is not implicitly added ahead of trusted binaries.</violation>
</file>

<file name="configs/riscv-cross-file">

<violation number="1" location="configs/riscv-cross-file:8">
P1: `@GLOBAL_SOURCE_ROOT@` is not a valid Meson cross-file variable and won't be substituted. The build script copies this file directly without preprocessing. Consider using absolute paths or add a preprocessing step to substitute this placeholder before running meson.</violation>
</file>

<file name="virtio-input.c">

<violation number="1" location="virtio-input.c:168">
P2: Wraparound arithmetic is off by one. When handling uint16_t index wraparound, adding `UINT16_MAX` (65535) is incorrect; it should be `UINT16_MAX + 1` (65536) or `(1U << 16)` to properly account for the full 16-bit range.</violation>

<violation number="2" location="virtio-input.c:207">
P1: Missing bounds validation on `vq_desc.addr` before memory write. A malicious guest could provide an out-of-bounds address, causing writes outside the allocated RAM. Add a check: `if (vq_desc.addr + sizeof(struct virtio_input_event) > RAM_SIZE) return false;`</violation>

<violation number="3" location="virtio-input.c:666">
P1: Missing bounds check on `vinput_dev_cnt` before array access. If `virtio_input_init` is called more than `VINPUT_DEV_CNT` (2) times, this will cause an out-of-bounds write to `vinput_dev[]`. Add a bounds check before assignment.</violation>
</file>

<file name="window-sw.c">

<violation number="1" location="window-sw.c:245">
P1: Missing NULL check after `vgpu_get_resource_2d()`. If the resource is not found, the function returns NULL and accessing `resource->format` will cause a crash.</violation>

<violation number="2" location="window-sw.c:271">
P1: Missing NULL check after `malloc()`. If memory allocation fails, `memcpy` on line 263 will dereference NULL and crash.</violation>
</file>

<file name="virtio-gpu-sw.c">

<violation number="1" location="virtio-gpu-sw.c:24">
P1: Memory leak: `res_2d` is already created and added to the resource list when image allocation fails. Call `vgpu_destroy_resource_2d(request->resource_id)` before returning to properly clean up the resource.</violation>

<violation number="2" location="virtio-gpu-sw.c:209">
P1: Potential NULL pointer dereference: `res_2d->iovec[0].iov_base` is accessed without checking if `res_2d->iovec` is non-NULL. If transfer is called before backing memory is attached, this will crash. Add a null check before accessing iovec.</violation>
</file>

<file name="window-events.c">

<violation number="1" location="window-events.c:145">
P1: The return value of `sdl_key_to_linux_key` (-1 for unmapped keys) is not validated before being passed to `virtio_input_update_key`. This causes -1 to be implicitly converted to `UINT32_MAX` and sent as an invalid key code to the guest.</violation>

<violation number="2" location="window-events.c:150">
P1: Same issue as SDL_KEYDOWN: the return value of `sdl_key_to_linux_key` (-1 for unmapped keys) is not validated before being passed to `virtio_input_update_key`.</violation>

<violation number="3" location="window-events.c:155">
P2: The return value of `sdl_key_to_linux_key` is not validated before being passed to `virtio_input_update_mouse_button_state`. Unmapped mouse buttons (scroll wheel, side buttons) will result in invalid button codes.</violation>

<violation number="4" location="window-events.c:160">
P2: Same issue as SDL_MOUSEBUTTONDOWN: the return value of `sdl_key_to_linux_key` is not validated before being passed to `virtio_input_update_mouse_button_state`.</violation>
</file>

<file name="scripts/build-image.sh">

<violation number="1" location="scripts/build-image.sh:47">
P1: Missing `ASSERT` wrapper on merge_tool command. If the config merge fails, the script will continue with an incorrect or incomplete Buildroot configuration, leading to confusing build failures later.</violation>

<violation number="2" location="scripts/build-image.sh:190">
P2: Missing `ASSERT` wrapper on `meson install` command. If installation fails, the script continues silently, potentially resulting in incomplete package installation.</violation>
</file>

<file name="virtio-gpu.c">

<violation number="1" location="virtio-gpu.c:139">
P1: Missing bounds validation for guest address. The function converts guest addresses to host pointers without checking if `addr` is within valid RAM bounds. This could allow a malicious guest to cause out-of-bounds memory access by providing an invalid address in virtqueue descriptors.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@@ -0,0 +1,18 @@
[binaries]
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: @GLOBAL_SOURCE_ROOT@ is not a valid Meson cross-file variable and won't be substituted. The build script copies this file directly without preprocessing. Consider using absolute paths or add a preprocessing step to substitute this placeholder before running meson.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At configs/riscv-cross-file, line 8:

<comment>`@GLOBAL_SOURCE_ROOT@` is not a valid Meson cross-file variable and won't be substituted. The build script copies this file directly without preprocessing. Consider using absolute paths or add a preprocessing step to substitute this placeholder before running meson.</comment>

<file context>
@@ -0,0 +1,18 @@
+    python = '/usr/bin/python3'
+
+[properties]
+    pkg_config_libdir = ['@GLOBAL_SOURCE_ROOT@' / '../buildroot/output/host/riscv32-buildroot-linux-gnu/sysroot/usr/local/lib/pkgconfig',
+                         '@GLOBAL_SOURCE_ROOT@' / '../buildroot/output/host/riscv32-buildroot-linux-gnu/sysroot/usr/share/pkgconfig/',
+                         '@GLOBAL_SOURCE_ROOT@' / '../buildroot/output/host/riscv32-buildroot-linux-gnu/sysroot/usr/lib/pkgconfig/'
</file context>
Fix with Cubic

}

/* Write event into guest buffer directly */
ev = (struct virtio_input_event *) ((uintptr_t) vinput->ram +
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Missing bounds validation on vq_desc.addr before memory write. A malicious guest could provide an out-of-bounds address, causing writes outside the allocated RAM. Add a check: if (vq_desc.addr + sizeof(struct virtio_input_event) > RAM_SIZE) return false;

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At virtio-input.c, line 207:

<comment>Missing bounds validation on `vq_desc.addr` before memory write. A malicious guest could provide an out-of-bounds address, causing writes outside the allocated RAM. Add a check: `if (vq_desc.addr + sizeof(struct virtio_input_event) > RAM_SIZE) return false;`</comment>

<file context>
@@ -0,0 +1,670 @@
+        }
+
+        /* Write event into guest buffer directly */
+        ev = (struct virtio_input_event *) ((uintptr_t) vinput->ram +
+                                            vq_desc.addr);
+        ev->type = input_ev[i].type;
</file context>
Fix with Cubic


void virtio_input_init(virtio_input_state_t *vinput)
{
vinput->id = vinput_dev_cnt;
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Missing bounds check on vinput_dev_cnt before array access. If virtio_input_init is called more than VINPUT_DEV_CNT (2) times, this will cause an out-of-bounds write to vinput_dev[]. Add a bounds check before assignment.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At virtio-input.c, line 666:

<comment>Missing bounds check on `vinput_dev_cnt` before array access. If `virtio_input_init` is called more than `VINPUT_DEV_CNT` (2) times, this will cause an out-of-bounds write to `vinput_dev[]`. Add a bounds check before assignment.</comment>

<file context>
@@ -0,0 +1,670 @@
+
+void virtio_input_init(virtio_input_state_t *vinput)
+{
+    vinput->id = vinput_dev_cnt;
+    vinput_dev_cnt++;
+
</file context>
Fix with Cubic

memcpy(&display->cursor, resource, sizeof(struct vgpu_resource_2d));
size_t pixels_size = sizeof(uint32_t) * resource->width * resource->height;
free(display->cursor_img);
display->cursor_img = malloc(pixels_size);
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Missing NULL check after malloc(). If memory allocation fails, memcpy on line 263 will dereference NULL and crash.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At window-sw.c, line 271:

<comment>Missing NULL check after `malloc()`. If memory allocation fails, `memcpy` on line 263 will dereference NULL and crash.</comment>

<file context>
@@ -0,0 +1,357 @@
+    memcpy(&display->cursor, resource, sizeof(struct vgpu_resource_2d));
+    size_t pixels_size = sizeof(uint32_t) * resource->width * resource->height;
+    free(display->cursor_img);
+    display->cursor_img = malloc(pixels_size);
+    display->cursor.image = display->cursor_img;
+    memcpy(display->cursor_img, resource->image, pixels_size);
</file context>
Suggested change
display->cursor_img = malloc(pixels_size);
display->cursor_img = malloc(pixels_size);
if (!display->cursor_img) {
fprintf(stderr, "%s(): failed to allocate cursor image\n", __func__);
window_unlock_mutex(scanout_id);
return -1;
}
Fix with Cubic


static int cursor_update_sw(int scanout_id, int res_id, int x, int y)
{
struct vgpu_resource_2d *resource = vgpu_get_resource_2d(res_id);
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Missing NULL check after vgpu_get_resource_2d(). If the resource is not found, the function returns NULL and accessing resource->format will cause a crash.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At window-sw.c, line 245:

<comment>Missing NULL check after `vgpu_get_resource_2d()`. If the resource is not found, the function returns NULL and accessing `resource->format` will cause a crash.</comment>

<file context>
@@ -0,0 +1,357 @@
+
+static int cursor_update_sw(int scanout_id, int res_id, int x, int y)
+{
+    struct vgpu_resource_2d *resource = vgpu_get_resource_2d(res_id);
+
+    /* Convert virtio-gpu resource format to SDL format */
</file context>
Suggested change
struct vgpu_resource_2d *resource = vgpu_get_resource_2d(res_id);
struct vgpu_resource_2d *resource = vgpu_get_resource_2d(res_id);
if (!resource) {
fprintf(stderr, "%s(): invalid resource id %d\n", __func__, res_id);
return -1;
}
Fix with Cubic

@@ -0,0 +1,4 @@
#!/usr/bin/bash

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Prevent LD_LIBRARY_PATH from acquiring a leading empty entry (which adds the current directory to the search path) when the variable is initially unset.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At target/run.sh, line 3:

<comment>Prevent LD_LIBRARY_PATH from acquiring a leading empty entry (which adds the current directory to the search path) when the variable is initially unset.</comment>

<file context>
@@ -0,0 +1,4 @@
+#!/usr/bin/bash
+
+export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
+export PATH=$PATH:/usr/local/bin/
</file context>
Suggested change
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}/usr/local/lib
Fix with Cubic

/* Handle if the available index has overflowed and returned to the
* beginning */
if (new_avail < queue->last_avail)
flattened_avail_idx += UINT16_MAX;
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Wraparound arithmetic is off by one. When handling uint16_t index wraparound, adding UINT16_MAX (65535) is incorrect; it should be UINT16_MAX + 1 (65536) or (1U << 16) to properly account for the full 16-bit range.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At virtio-input.c, line 168:

<comment>Wraparound arithmetic is off by one. When handling uint16_t index wraparound, adding `UINT16_MAX` (65535) is incorrect; it should be `UINT16_MAX + 1` (65536) or `(1U << 16)` to properly account for the full 16-bit range.</comment>

<file context>
@@ -0,0 +1,670 @@
+    /* Handle if the available index has overflowed and returned to the
+     * beginning */
+    if (new_avail < queue->last_avail)
+        flattened_avail_idx += UINT16_MAX;
+
+    /* Check if need to wait until the driver supplies new buffers */
</file context>
Suggested change
flattened_avail_idx += UINT16_MAX;
flattened_avail_idx += (uint32_t) UINT16_MAX + 1;
Fix with Cubic

}
case SDL_MOUSEBUTTONUP: {
linux_key = sdl_key_to_linux_key(e.button.button);
virtio_input_update_mouse_button_state(linux_key, false);
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Same issue as SDL_MOUSEBUTTONDOWN: the return value of sdl_key_to_linux_key is not validated before being passed to virtio_input_update_mouse_button_state.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At window-events.c, line 160:

<comment>Same issue as SDL_MOUSEBUTTONDOWN: the return value of `sdl_key_to_linux_key` is not validated before being passed to `virtio_input_update_mouse_button_state`.</comment>

<file context>
@@ -0,0 +1,170 @@
+            }
+            case SDL_MOUSEBUTTONUP: {
+                linux_key = sdl_key_to_linux_key(e.button.button);
+                virtio_input_update_mouse_button_state(linux_key, false);
+                break;
+            }
</file context>
Suggested change
virtio_input_update_mouse_button_state(linux_key, false);
if (linux_key >= 0)
virtio_input_update_mouse_button_state(linux_key, false);
Fix with Cubic

}
case SDL_MOUSEBUTTONDOWN: {
linux_key = sdl_key_to_linux_key(e.button.button);
virtio_input_update_mouse_button_state(linux_key, true);
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The return value of sdl_key_to_linux_key is not validated before being passed to virtio_input_update_mouse_button_state. Unmapped mouse buttons (scroll wheel, side buttons) will result in invalid button codes.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At window-events.c, line 155:

<comment>The return value of `sdl_key_to_linux_key` is not validated before being passed to `virtio_input_update_mouse_button_state`. Unmapped mouse buttons (scroll wheel, side buttons) will result in invalid button codes.</comment>

<file context>
@@ -0,0 +1,170 @@
+            }
+            case SDL_MOUSEBUTTONDOWN: {
+                linux_key = sdl_key_to_linux_key(e.button.button);
+                virtio_input_update_mouse_button_state(linux_key, true);
+                break;
+            }
</file context>
Suggested change
virtio_input_update_mouse_button_state(linux_key, true);
if (linux_key >= 0)
virtio_input_update_mouse_button_state(linux_key, true);
Fix with Cubic

cp ../configs/riscv-cross-file .
ASSERT meson -Ddrmkms=true --cross-file riscv-cross-file build/riscv
ASSERT meson compile -C build/riscv
DESTDIR=$BUILDROOT_OUT/host/riscv32-buildroot-linux-gnu/sysroot meson install -C build/riscv
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Missing ASSERT wrapper on meson install command. If installation fails, the script continues silently, potentially resulting in incomplete package installation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/build-image.sh, line 190:

<comment>Missing `ASSERT` wrapper on `meson install` command. If installation fails, the script continues silently, potentially resulting in incomplete package installation.</comment>

<file context>
@@ -133,11 +170,72 @@ while [[ $# -gt 0 ]]; do
+    cp ../configs/riscv-cross-file .
+    ASSERT meson -Ddrmkms=true --cross-file riscv-cross-file build/riscv
+    ASSERT meson compile -C build/riscv
+    DESTDIR=$BUILDROOT_OUT/host/riscv32-buildroot-linux-gnu/sysroot meson install -C build/riscv
+    DESTDIR=../../../directfb meson install -C build/riscv
+    popd
</file context>
Fix with Cubic

res_2d->stride = STRIDE_SIZE;
res_2d->image = malloc(bytes_per_pixel * (request->width + res_2d->stride) *
request->height);
res_2d->image = malloc(res_2d->stride * request->height);
Copy link
Collaborator

@shengwen-tw shengwen-tw Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original implementation of my virtio-gpu used pixman. When I attempted to remove the pixman dependency, I introduced a hard-coded STRIDE_SIZE of 4096 as @visitorckw pointed out. After reviewing the VirtIO GPU spec. again, I think this may be incorrect. shengwen-tw@1120d55

In virtio-gpu 2D mode (VIRTIO_GPU_CMD_RESOURCE_CREATE_2D, VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D, and VIRTIO_GPU_CMD_SET_SCANOUT), the protocol does not define an explicit stride field. This suggests that the expected memory layout might be just tightly packed, i.e., each row is exactly width * bytes_per_pixel with no additional per-row padding. If so, explicit stride handling may be unnecessary or even incorrect.

@Mes0903 :
Could you help run some experiments to confirm whether we can remove the stride handling entirely? For example, by checking the size of the received buffer and testing what happens when STRIDE_SIZE is set to 0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet