mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-10 11:31:41 -10:00
211263f008d6d9b38f77bfc22a94de42487fcdfe
35
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
42a6c7f167 | vulkan: support swapchains with more than eight images (#19186) | ||
|
|
4da610d390 |
samples/database: stub msg_hash_us_index_init
The msg_hash string-table refactor made msg_hash_set_uint() call msg_hash_us_index_init() (from intl/msg_hash_us.c) under MSG_HASH_HAVE_STRTAB, which is enabled for every non-MSVC compiler. The database sample links msg_hash.o but deliberately stubs the intl layer (msg_hash_to_str_us, msg_hash_get_help_us_enum) rather than pulling in the ~1 MB of string tables, so the new call left an undefined reference at link time. Add a matching no-op stub for msg_hash_us_index_init. With msg_hash_to_str_us() already stubbed to return "", the string-table index is never consulted, so the empty body is correct and the sample still avoids intl/msg_hash_us.c. |
||
|
|
4e26c8875a |
gfx/gfx_widgets: lock msg_queue against producer-producer race
dispgfx_widget_t::msg_queue is a fifo_buffer_t storing
disp_widget_msg_t* pointers. Pre-fix it had no dedicated lock,
yet the producer (gfx_widgets_msg_queue_push) is reachable from
threads other than the main thread, and the lock that *did*
exist (current_msgs_lock) protects current_msgs[] -- the
displayed-message deque -- not the FIFO.
Producer reachability:
1. runloop.c:runloop_msg_queue_push. Holds RUNLOOP_MSG_QUEUE_
LOCK across the call. Reachable from 40+ files; the
threaded task system at libretro-common/queues/task_queue.c
runs a worker thread that pushes via this entry point.
2. runloop.c:runloop_task_msg_queue_push. Same lock.
3. gfx/video_driver.c:video_driver_frame. Acquires
RUNLOOP_MSG_QUEUE_LOCK to drain runloop_st->msg_queue,
RELEASES it, then calls gfx_widgets_msg_queue_push without
the lock.
Path 3 runs on the main thread; path 1 can run on any thread.
RUNLOOP_MSG_QUEUE_LOCK guards runloop_st->msg_queue, NOT
p_dispwidget->msg_queue -- the lock name reflects the runloop
struct it was named after. So path 3's fifo_write (no lock)
can race with path 1's fifo_write (lock held but irrelevant
to path 3).
The consumer (gfx_widgets_iterate's fifo_read) is called from
runloop.c:6048 inside RUNLOOP_MSG_QUEUE_LOCK and from
gfx_widgets.c:973 inside current_msgs_lock -- but neither lock
is held by path 3, so consumer fifo_reads can observe a half-
updated `end` cursor from path 3's fifo_writes.
Outcome of a race: torn `end` cursor publishes a bad pointer
in the disp_widget_msg_t* slots. The consumer dereferences
that as disp_widget_msg_t* in gfx_widgets_iterate, producing
use-after-free / segfault. On x86 TSO the race is masked most
of the time by hardware ordering; on Win-on-ARM / Apple
Silicon (AArch64) it surfaces more often.
Collision probability is low (~1 per 14 hours of normal use,
estimated from frame rate * task message rate * fifo_write
window) but the worst case is crash-class.
Fix
---
Add slock_t* msg_queue_lock to dispgfx_widget_t. Acquire it
around every fifo_write (in gfx_widgets_msg_queue_push), every
fifo_read (in gfx_widgets_iterate), and the avail-check that
gates each one. msg_queue_lock is the inner lock when both
it and current_msgs_lock are held; lock order is consistent
across all call sites.
The outer FIFO_*_AVAIL fast-path checks that used to wrap the
producer and consumer function bodies have been dropped.
Reading the FIFO cursors outside msg_queue_lock is itself a
data race against the locked writes -- TSan-detectable; benign
on x86 TSO but real on weak-memory hardware. The locked
re-check at the actual fifo_write / fifo_read site is the
correctness gate.
Removing the producer's outer gate also fixes a latent
behaviour bug: the update-existing branch (else clause in
gfx_widgets_msg_queue_push) does not write to the FIFO -- it
only mutates an already-tracked widget -- so suppressing it on
FIFO-full was wrong. Existing widgets now get updated
regardless of FIFO state.
The producer's spawn-new branch may now race-lose the avail
re-check after allocating msg_widget (concurrent producer
filled the last slot between unlocked allocation and locked
write). In that case the function frees the just-allocated
widget via gfx_widgets_msg_queue_free + free and returns. A
forward declaration of gfx_widgets_msg_queue_free is added near
the top of the file because that function is defined further
down.
Regression test
---------------
samples/gfx/gfx_widgets_msg_queue_race/ mirrors the post-fix
locking discipline of gfx_widgets_msg_queue_push and
gfx_widgets_iterate's FIFO interaction in isolation. Two
producer threads + one consumer thread + 500K iterations per
producer; smoke check verifies single-threaded FIFO order.
Wired into .github/workflows/Linux-samples-gfx.yml as a TSan
lane (CC=clang, SANITIZER=thread, TSAN_OPTIONS=halt_on_error=1)
following the gfx_thumbnail_status_atomic_test convention.
TSan instruments every memory access on fifo_buffer_t::end and
::first, so a future regression that drops the lock around any
of the producer / consumer / avail-check sites will be flagged
as a data race and fail the workflow.
Verified locally
----------------
- samples/gfx/gfx_widgets_msg_queue_race builds and runs
clean as C (gcc -O0), C++11 (g++ -std=c++11 -xc++), and
Clang under -fsanitize=thread.
- TSan with halt_on_error=1: clean exit (post-fix lock
discipline causes no observed races across 500K * 2
iterations).
- Mutation test: removing the slock_lock/unlock around the
producer's fifo_write makes TSan fire on the first
collision, validating that the test is sensitive to the
exact regression we're guarding against.
- gfx/gfx_widgets.c syntax-checks clean with HAVE_THREADS +
HAVE_GFX_WIDGETS via gcc -fsyntax-only against the
libretro-common headers.
|
||
|
|
ac04c9189b |
gfx/gfx_thumbnail: port hand-rolled atomic shim to retro_atomic.h
The file had its own local __atomic_* / _Interlocked / volatile shim guarding the cross-thread .status field, written before retro_atomic.h existed. Replace it with the portable API now that the header is upstream ( |
||
|
|
832250be0a | Buildfix for CI | ||
|
|
dd3204d086 |
gfx/drivers_context/{wayland,w,x}_vk_ctx: fix UAF + double-free + CI
Found during the audit of the Vulkan context shims after
|
||
|
|
f5c7df53c6 |
gfx/common/vulkan: plug partial-init leak in emulated mailbox + CI
Found during the v2 audit of gfx/common/vulkan_common.c, deferred
from the v3 patch (
|
||
|
|
29080418c3 |
gfx/drivers_shader: cap arrayed texture-semantic indices in slang + CI
Found during a continued audit of gfx/drivers_shader/shader_vulkan.cpp and slang_process.cpp after the v3 vulkan-driver fixes ( |
||
|
|
da5e650ccf |
gfx/vulkan: fix three heap-corruption bugs in driver and common layer + CI
Three independent issues found during a focused audit of gfx/drivers/
vulkan.c and gfx/common/vulkan_common.c, all in the same memory-safety
class (writes past compile-time-sized arrays or undersized
allocations). Submitting together because they share the same audit
context and none stands alone particularly well. Adds AddressSanitizer
regression tests for each under samples/gfx/, wired into a new
.github/workflows/Linux-samples-gfx.yml CI job.
* vulkan_find_device_extensions: heap overflow
(gfx/common/vulkan_common.c)
The required-extension list is currently written twice -- once via
memcpy at the top of the body, then again in a per-element loop
that appears to be left over from an earlier refactor. The
instance-side counterpart vulkan_find_instance_extensions only
logs in its loop, confirming the divergence. The redundant write
consumes more slots in the caller's `enabled` buffer than the slot
accounting expects.
Inside vulkan_context_create_device_wrapper the buffer is sized
for (info.enabledExtensionCount + ARRAY_SIZE(required) +
ARRAY_SIZE(optional)) entries, but the function actually writes
(enabledExtensionCount + 2*required + optional) entries
worst-case. With current values (1 required, 3 optional) that is
a one-element heap overflow at the end of the malloc'd block
whenever a libretro core uses the Vulkan HW context-negotiation
interface and the GPU supports at least one optional extension.
The other call site in vulkan_context_init_device uses a stack
buffer of size 8 and stays inside it today, but only by a margin
of three.
The duplicate writes also produce duplicate entries in
ppEnabledExtensionNames passed to vkCreateDevice, which the spec
forbids (VUID-VkDeviceCreateInfo-ppEnabledExtensionNames-01840);
strict validation layers will have been complaining.
Drop the per-element loop -- the early vulkan_find_extensions()
check at the top of the function already validates that all
required extensions are present, so the per-element re-check has
never done any work. Keep the debug log.
* vulkan_create_swapchain: OOB writes from unclamped image count
(gfx/common/vulkan_common.c)
vulkan_create_swapchain calls vkGetSwapchainImagesKHR twice --
first to query the count, then to fill swapchain_images[] -- and
never clamps the count between the two calls. swapchain_images,
swapchain_fences, the four swapchain_*_semaphores arrays,
readback.staging[] and vk->swapchain[] are all sized at compile
time to VULKAN_MAX_SWAPCHAIN_IMAGES (8). If a driver returns
more images than that the second vkGetSwapchainImagesKHR writes
past swapchain_images[], and every loop bounded by
num_swapchain_images (~12 sites across init/deinit/textures/
buffers/descriptor pools/command buffers/readback and direct
vk->swapchain[i] accesses) walks past its array.
Two clamps. First, cap desired_swapchain_images to
VULKAN_MAX_SWAPCHAIN_IMAGES before vkCreateSwapchainKHR so we
never ask a well-behaved driver for more images than we can
hold. Second, clamp num_swapchain_images between the two
vkGetSwapchainImagesKHR calls to handle drivers that return
more images than were requested -- the spec permits this.
* vulkan_create_texture: 32-bit overflow in staging-buffer sizing
(gfx/drivers/vulkan.c)
buffer_width = width * bpp and buffer_info.size = buffer_width *
height are both unsigned * unsigned in 32-bit before being
widened to VkDeviceSize on assignment. With dimensions large
enough to wrap (e.g. width=65536, height=16385, bpp=4 ->
0x1_0004_0000 wraps to 0x40000), the staging buffer is allocated
at the wrapped (small) size while the per-row upload memcpy loop
later in the same function walks the unmodified width x height.
The mapped region is smaller than what the loop writes, so the
memcpy walks past the mapping into adjacent heap memory.
Reachable from libretro cores supplying oversized
retro_framebuffer dimensions and from vulkan_load_texture /
vulkan_set_texture_frame, which take dimensions originating in
image decoders.
Compute the staging-buffer size in 64-bit, and widen the upload
loop's stride and per-row copy size to size_t. VkDeviceSize is
already 64-bit so the assignment to buffer_info.size is now
correct end-to-end. The loop's pointer math was already
implicitly widened on 64-bit hosts but is now unconditionally
correct on 32-bit hosts (3DS, Vita, PSP, Wii, Wii U, older
Android, 32-bit Windows) as well.
* samples/gfx/, .github/workflows/Linux-samples-gfx.yml
Three regression tests, one per fix above, following the pattern
established by the security regression tests under samples/tasks/
(archive_name_safety_test, http_method_match_test,
video_shader_wildcard_test, input_remap_bounds_test,
bsv_replay_bounds_test, bps_patch_bounds_test). Each test keeps
a verbatim copy of the relevant post-fix predicate, demarcated by
`=== verbatim copy ===` markers; an `IMPORTANT: ... must follow`
comment documents the convention. Plain C, asserts manually,
exits nonzero on failure -- matches the harness the existing
Linux-samples-{tasks,libretro-{common,db}}-samples.yml workflows
understand.
vulkan_extension_count_test (5 cases): create_device_wrapper-
shaped caller, init_device-shaped caller, GPU supporting no
optional extensions, GPU missing a required extension, and the
prepushed-via-negotiation-interface case. Verified pre/post-
patch discrimination: under the pre-fix double-write shape the
create_device_wrapper case ASan-aborts with heap-buffer-overflow.
vulkan_swapchain_clamp_test (5 cases): well-behaved 3-image
driver, at-capacity boundary, 9-image (MAX+1) driver,
pathological 64-image driver, and the request-side cap across
six input values. Verified pre/post-patch discrimination: under
the pre-fix no-clamp shape the 9-image case ASan-aborts on the
second vkGetSwapchainImagesKHR fill.
vulkan_texture_size_test (6 cases): typical 320x240, 4K RGBA,
the smoking-gun 65536x16385x4 wrap case, an assorted-shapes
table, the per-row upload-loop strides under an ASan-instrumented
buffer, and a 64-bit-result sanity check. Verified pre/post-
patch discrimination: under the pre-fix 32-bit arithmetic the
overflow cases produce arithmetic mismatches against the 64-bit
reference.
Linux-samples-gfx.yml is a near-verbatim copy of
Linux-samples-tasks.yml, runs each test under
-fsanitize=address with a per-step explanation of what the test
is regression-testing and the `verbatim copy must follow`
reminder. ~3 seconds per test on Ubuntu 24 with system gcc.
|
||
|
|
c41e955464 |
tasks/task_patch: bound BPS patch action-loop and size-prelude against attacker-controlled inputs
bps_apply_patch (tasks/task_patch.c) implemented BPS patch
application without bounds checks on any of the per-command
reads or writes. The action loop:
while (bps.modify_offset < bps.modify_length - 12)
{
size_t _len = bps_decode(&bps); /* attacker-chosen */
unsigned mode = _len & 3;
_len = (_len >> 2) + 1;
switch (mode)
{
case TARGET_READ:
while (_len--) {
uint8_t data = bps_read(&bps);
bps.target_data[bps.output_offset++] = data; /* OOB write */
}
case SOURCE_READ:
while (_len--) {
uint8_t data = bps.source_data[bps.output_offset]; /* OOB read */
bps.target_data[bps.output_offset++] = data;
}
case SOURCE_COPY:
bps.source_offset += offset; /* unbounded signed offset */
while (_len--) {
uint8_t data = bps.source_data[bps.source_offset++]; /* OOB read */
bps.target_data[bps.output_offset++] = data;
}
case TARGET_COPY:
bps.target_offset += offset;
while (_len--) {
uint8_t data = bps.target_data[bps.target_offset++];
bps.target_data[bps.output_offset++] = data;
}
}
}
A malicious .bps could:
- Write attacker-chosen bytes far past the malloc'd
target_data buffer (heap-buffer-overflow WRITE) via any
of the four modes.
- Read past source_data via SOURCE_COPY's unbounded signed
offset. Negative offsets underflow source_offset to a
huge size_t; positive offsets push it past
source_length. The leaked bytes flow into target_data
and the patch checksum, exposing heap contents.
- Read past modify_data via TARGET_READ when _len exceeds
the remaining patch bytes. The action-loop guard
confirms only "at least one command's worth of bytes",
not the full read range.
The size prelude (modify_source_size / modify_target_size /
modify_markup_size, decoded by bps_decode immediately after
the "BPS1" header) was also unchecked:
- modify_target_size declared as size_t silently truncated
the uint64 bps_decode result on 32-bit hosts. A value of
0x100000000 wraps to 0; the subsequent malloc(0) returns
a tiny allocation while bps.target_length is set to the
raw uint64. The next target_data[output_offset++] write
OOB by the truncated delta.
- modify_markup_size was used as a loop bound consuming
bytes from modify_data via bps_read. A value above the
remaining patch bytes ran the read loop off the end of
modify_data.
Reachability: user has soft-patching enabled (the default
when a .bps file with the same basename as the loaded ROM is
present) and loads a ROM whose .bps was supplied by an
attacker. Same threat class as CDFS / CHD / BSV file-load
bugs.
Fix:
1. Capture the three size-prelude values as raw uint64,
reject anything above SIZE_MAX (so the size_t cast is
guaranteed lossless), and reject markup_size that would
drive bps_read past modify_length.
2. At the top of every action-loop iteration, bound _len
against (target_length - output_offset). This single
bound covers the target_data write in all four modes.
3. In each mode, bound the source-side read range:
- SOURCE_READ: output_offset + _len <= source_length
- TARGET_READ: modify_offset + _len <= modify_length - 12
- SOURCE_COPY: source_offset (post-offset-add) + _len
<= source_length
- TARGET_COPY: target_offset (post-offset-add) + _len
<= target_length
Adds samples/tasks/bps_patch/bps_patch_bounds_test as a
regression test, registered in
.github/workflows/Linux-samples-tasks.yml as an ASan-enabled
step. Five cases covering OOB-write rejection, OOB-read
rejection, legitimate operations, and the exact-capacity
boundary.
Test follows the existing samples/tasks pattern (verbatim
copy of the post-fix predicates with maintenance-contract
comment, ASan as the discriminator). Verified to fire
under ASan when the bounds are removed: heap-buffer-overflow
WRITE of size 1 at offset 4 of a 4-byte allocation in
apply_action_loop. With the bounds the test passes clean.
|
||
|
|
003a1ff74b |
input/bsv: bound key_event_count and input_event_count against array size
bsv_movie_handle_read_input_event's read path read attacker-
controlled event counts straight from the .bsv replay file
and used them as loop bounds, indexing into fixed-size
arrays:
uint8_t key_event_count; (.bsv format)
uint16_t input_event_count; (.bsv format)
bsv_key_data_t key_events[128]; (struct bsv_movie)
bsv_input_data_t input_events[512]; (struct bsv_movie)
Pre-this-patch the read loops at lines 822-836 (key) and
848-862 (input) iterated count-times into the fixed-size
arrays with no upper-bound check. A malformed .bsv could
supply:
- key_event_count = 255 -> (255-128)*sizeof(bsv_key_data_t)
= 1524 bytes of OOB heap-write past
key_events[]
- input_event_count = 65535 ->
(65535-512)*sizeof(bsv_input_data_t)
= 520184 bytes of OOB heap-write
past input_events[]
bsv_movie_t is heap-allocated via calloc in
tasks/task_movie.c::bsv_movie_init_internal so this is a
heap-buffer-overflow with attacker-chosen 8- or 12-byte
payloads at attacker-chosen offsets, producing corruption
of the rest of the bsv_movie_t struct (rewind state,
statestream pointers, save buffers) and far beyond it.
Reachability: the user opens a malformed .bsv file via
Menu -> "Load Replay File" or the --bsvplay command-line
argument. Same threat class as CDFS / CHD / BPS file-load
bugs -- requires the user to deliberately load attacker-
supplied content. Initial audit notes flagged this as
netplay-reachable but verification of network/netplay/
netplay_frontend.c shows that netplay's "replay" terminology
refers to internal frame-replay from the netplay ring buffer
and does not invoke the .bsv file-read path; this is a
file-loading bug only.
Fix: in input/bsv/bsvmovie.c, reject the file as malformed
and end playback if either count exceeds its array's
ARRAY_SIZE. A legitimate writer cannot produce more events
than the array holds because the writer's append path at
lines 489-490 / 475-476 shares the same backing array.
Adds samples/tasks/bsv_replay/bsv_replay_bounds_test as a
regression test, registered in
.github/workflows/Linux-samples-tasks.yml as an ASan-enabled
step. Four cases covering legitimate counts up to and
including the cap, and out-of-range counts being rejected
without writes.
Test follows the existing samples/tasks pattern (verbatim
copy of the post-fix predicate with maintenance-contract
comment, ASan as the discriminator). Verified to fire
under ASan when the predicate always returns true:
heap-buffer-overflow WRITE of size 1 at offset 1536 of a
1536-byte allocation in simulate_key_event_read. With the
predicate the test passes clean.
Note on writer side: the append site at line 489
movie->input_events[movie->input_event_count++] = data;
has no bound check either, but it's not fed by attacker
data and a single frame is unlikely to produce more than
512 events in practice. Not addressed by this commit; left
for a separate audit pass.
|
||
|
|
cba3a9c909 |
input: bound input_remap_ids[] against analog_value[][8] OOB write
input_driver.c's poll-time remap loop indexed
mapper->analog_value[i][n] using values from
settings->uints.input_remap_ids[i][k] without proper bounds in
two places:
1. Button -> analog branch (line ~7392):
handle->analog_value[i][remap_button - RARCH_FIRST_CUSTOM_BIND] = ...
No bound on remap_button at all. Reachable values:
[RARCH_FIRST_CUSTOM_BIND, RARCH_UNMAPPED) i.e. [16, 1024),
producing remap_axis_bind in [0, 1008). Indexing past the
8-element row writes int16_t values up to ~2014 bytes past
the end of analog_value, into adjacent input_mapper_t
fields (keys[], key_button[], buttons[]) and beyond into
adjacent input_driver_state_t fields.
2. Analog -> analog branch (line ~7427):
if (remap_axis_bind < sizeof(handle->analog_value[i]))
handle->analog_value[i][remap_axis_bind] = ...
The sizeof() returns 16 (the byte size of the int16_t[8]
row), not the element count. Indices 8..15 passed the
check and OOB-wrote into the next user's analog_value row,
or past the array entirely for the last user.
Both sites read remap_button / remap_axis from
settings->uints.input_remap_ids[i][k], which is loaded from
.rmp files via input_remapping_load_file in configuration.c.
Pre-this-patch the loader fed config_get_int's result directly
into storage with no range check, so a malformed .rmp could
plant any int there. An attacker who can deliver a .rmp
alongside a ROM (a common distribution channel for
"controller config bundles") gets a 60Hz attacker-controlled
out-of-bounds write into adjacent input state -- including
the keys[] keyboard bitmask for the last user, which controls
which RetroArch hotkeys appear pressed.
Two-pronged fix:
- Load-time validation in input_remapping_load_file:
reject _remap values outside [0, RARCH_ANALOG_BIND_LIST_END)
(clamping to RARCH_UNMAPPED) before storing into
settings->uints.input_remap_ids[i][j]. Applied to both the
button-branch (j < RARCH_FIRST_CUSTOM_BIND) and the
analog-branch (j >= RARCH_FIRST_CUSTOM_BIND) of the
loader.
- Use-site bounds in input_driver.c: add an
ARRAY_SIZE-based bound to the previously-unguarded
button -> analog branch, and replace the broken sizeof()
bound with ARRAY_SIZE() in the analog -> analog branch.
Defence in depth: even if the load-time validator misses
a path (or a future caller writes to input_remap_ids[]
bypassing the loader), the use sites are now safe.
Adds samples/tasks/input_remap/input_remap_bounds_test as a
regression test, registered in
.github/workflows/Linux-samples-tasks.yml as an ASan-enabled
step. Seven cases covering the load-time clamp's accept and
reject paths, in-range writes going to the correct slot,
out-of-range writes being skipped without OOB, and the
specific historical sizeof-vs-ARRAY_SIZE bug shape.
Test follows the existing samples/tasks pattern (verbatim copy
of the post-fix predicates with a maintenance-contract comment;
ASan as the discriminator). Verified to fire under ASan when
the bound check is removed: heap-buffer-overflow WRITE of size
2 at offset 16 of a 16-byte allocation, in
do_button_to_analog_write. With the bound the test passes
clean.
|
||
|
|
bac68947b0 |
gfx/video_shader_parse: clamp replace_text length and prefix+_len in wildcard replacement
video_shader_replace_wildcards_impl built each replacement
value into a 256-byte stack buffer:
char replace_text[256];
_len = strlcpy(replace_text, source, sizeof(replace_text));
then unbounded:
memcpy(dst + prefix, replace_text, _len);
strlcpy(dst + prefix + _len, found + token_len,
PATH_MAX_LENGTH - prefix - _len);
Two related primitives were exposed:
1. strlcpy returns strlen(source) regardless of destination
capacity. When source resolved to a $CONTENT-DIR$ /
$PRESET-DIR$ / $CORE$ / $GAME$ value longer than 256
bytes (DIR_MAX_LENGTH is 4096, library_name and
path_basename are platform-dependent and routinely
exceed 256), _len was strlen(source). The memcpy then
read past the end of the 256-byte replace_text array
into adjacent stack memory.
2. The same _len fed (size_t)(PATH_MAX_LENGTH - prefix -
_len) on the next line. When prefix + _len exceeded
PATH_MAX_LENGTH the subtraction underflowed size_t to a
huge value and the strlcpy walked off the end of dst.
Even with the _len clamp from (1), a wildcard token
placed near the end of the input path (legitimate for
an attacker-supplied .slangp/.glslp) lets prefix +
REPLACE_BUF_SZ exceed PATH_MAX_LENGTH on its own.
The snprintf-chained branches (CORE_REQUESTED_ROTATION,
VIDEO_USER_ROTATION, VIDEO_FINAL_ROTATION,
SCREEN_ORIENTATION) had a sibling problem: snprintf returns
the would-have-written length, so _len += snprintf(...) on
a saturated buffer pushes _len past sizeof(replace_text)
even when the destination ate the entire size budget.
Reachable via shader presets the user opens. These come
from the online updater (typically slang-shaders or
common-shaders), the user's own preset directory, and from
content-bundled presets distributed alongside ROMs.
Fix: clamp at the use site so all the upstream branches are
covered in one place.
if (_len >= sizeof(replace_text))
_len = sizeof(replace_text) - 1;
if (prefix >= PATH_MAX_LENGTH || _len >= PATH_MAX_LENGTH - prefix)
break;
Adds samples/tasks/video_shader_parse/video_shader_wildcard_test
as a regression test, registered in
.github/workflows/Linux-samples-tasks.yml as an ASan-enabled
step. Four cases:
- short replacement produces correct output
- long source (600 chars) clamped without OOB read
- huge source (4000 chars) clamped without strlcpy underflow
- prefix near PATH_MAX_LENGTH correctly bails out
Test follows the existing samples/tasks pattern (verbatim
copy of the post-fix arithmetic block, ASan as the
discriminator) used by archive_name_safety_test and
http_method_match_test. Verified to fire under ASan when
the clamps are removed: stack-buffer-overflow READ of size
600 at offset 288 in replace_text (a 256-byte buffer).
With the clamps the test passes clean.
Note on test scope: the test exercises a verbatim copy of
the post-replacement arithmetic block from
video_shader_parse.c lines 344-358, not the full
video_shader_replace_wildcards_impl. The full function
depends on settings_t / runloop_state / video driver
globals that aren't tractable to stub for a unit test --
following the convention used by archive_name_safety_test
("if task_decompress.c amends the predicate, the copy here
must follow"), the test's verbatim block must track the
production block. A comment in the test points at the
production lines explicitly.
|
||
|
|
ae3292fe03 |
task_http: fix heap-buffer-overflow in GET fast-path + regression test
task_push_http_transfer_generic() decided whether a request is a "plain"
GET (and therefore subject to dedup via task_http_finder) by doing:
method = net_http_connection_method(conn);
if (memcmp(method, "GET", 3) == 0 && method[3] == '\0')
`method` is a pointer into a buffer that net_http_connection_new()
strdup'd from caller-supplied data; its allocation is exactly
strlen(method) + 1 bytes. Two cases overrun that buffer:
1) method == NULL. task_push_http_transfer_with_content() forwards the
caller-supplied `method` argument straight to net_http_connection_new()
without a NULL check; net_http_connection_new() itself permits a NULL
method (conn->method stays NULL). net_http_connection_method() then
returns NULL and the memcmp dereferences it.
2) method shorter than 3 chars (e.g. "" or a 1-char custom verb).
strdup allocates 1 or 2 bytes; memcmp reads 3 bytes regardless,
overrunning the heap allocation. AddressSanitizer reports:
==N==ERROR: AddressSanitizer: heap-buffer-overflow
READ of size 3 at <addr> thread T0
#0 ... memcmp
#1 task_push_http_transfer_generic tasks/task_http.c:274
<addr> is located 0 bytes after 2-byte region [...,...]
allocated by thread T0 here:
#0 ... strdup
#1 net_http_connection_new
The trailing `method[3] == '\0'` guard happens *after* memcmp's read,
so it cannot prevent the overflow.
Replace the unsafe pair with string_is_equal(), which is bounded by the
buffer's own NUL terminator and is already used elsewhere in this file
(see task_http_finder at line 242 and the existing #include of
<string/stdstring.h>). Add an explicit NULL guard for the method that
takes the existing error path; this also frees `conn` via the goto so
the existing ownership invariant on the error label is preserved.
Add a standalone regression test under samples/tasks/http/ and wire it
into Linux-samples-tasks CI. The test mirrors
samples/tasks/decompress/archive_name_safety_test.c: it keeps a
verbatim copy of the predicate (the dispatch decision is local to
task_push_http_transfer_generic and not in a public header) and
exercises it against the truth-table plus the bounds-safety regression
cases ("", "X", "GE", NULL). Inputs are strdup'd so each buffer's
allocation is exactly strlen+1, matching what net_http_connection_new()
does and making the pre-fix expression trip ASan in CI.
The Makefile honours SANITIZER= the same way samples/tasks/database/
does, and the new CI step builds with SANITIZER=address so a regression
to any unsafe expression is caught at the bounds level, not just the
truth-table level. Verified locally: with the post-fix predicate the
test passes clean under ASan; reverting the predicate to the pre-fix
memcmp causes ASan to abort with "heap-buffer-overflow ... READ of
size 3" on the empty-string case.
build-essential on ubuntu-latest already pulls libasan via gcc-13's
libgcc-13-dev, so no apt-get changes are needed in the workflow.
Found via a focused ASan harness around the task_http.c finish-path
ownership logic.
|
||
|
|
96ec45a82f |
samples/tasks: repair database demo, add decompress test
Two related changes under samples/tasks/.
samples/tasks/database: restore a buildable state
The database demo had bit-rotted to the point where neither the
C source nor the Makefile matched the current tree:
* main_msg_queue_push: retro_task_queue_msg_t gained a
retro_task_t* first parameter
* main_db_cb: retro_task_callback_t gained the same
* core_info_init_list: added a sixth bool *cache_supported
parameter
* Makefile: hash/rhash.c was renamed to hash/lrc_hash.c
* Makefile: 7zip source list was stale -- 7zIn.c was renamed to
7zArcIn.c, and BraIA64.c / CpuArch.c / Delta.c were added
* Makefile: missing sources for trans_stream*, rzip_stream,
features_cpu, rtime, rxml, m3u_file, logiqx_dat,
manual_content_scan, yxml
* link: missing -lm for roundf
Several symbols reached via task_push_dbscan are defined only
when RARCH_INTERNAL is set, which would transitively require
linking most of retroarch.c, frontend_driver.c, runloop.c,
configuration.c and the UI/video subsystems. Since this sample
exercises only the task queue, it provides lightweight stubs for:
* msg_hash_get_help_us_enum (real def in intl/msg_hash_us.c)
* msg_hash_to_str_us (real def in intl/msg_hash_us.c)
* config_get_ptr (real def in configuration.c)
* runloop_msg_queue_push (real def in runloop.c)
* retroarch_override_setting_is_set
* ui_companion_driver_notify_refresh
* video_display_server_set_window_progress
* frontend_driver_get_free_memory
* dir_list_new_special (real def in retroarch.c)
All stubs are either no-ops or return a conservative value that
lets the surrounding code bail gracefully; none of them are
exercised on the happy dbscan path.
After these changes the demo builds cleanly on a stock Ubuntu
host (build-essential, zlib1g-dev, libpthread), and both
`database_task` with no args and `database_task <5 dirs>` exit
with the expected status.
samples/tasks/decompress: new standalone regression test
Adds archive_name_safety_test, a 23-case predicate unit test for
the archive_name_is_safe() function in tasks/task_decompress.c.
This test was originally developed alongside the Zip Slip patch
in the first security round and held out of
libretro-common/samples/ because it exercises code in tasks/
rather than libretro-common/. samples/tasks/ is its natural
home.
The test keeps a verbatim copy of the predicate and validates it
against a table of safe / unsafe paths:
* safe: file.txt, dir/file.txt, a.b/c, ..foo, foo..
* unsafe: .., ../etc/passwd, foo/..//bar, /etc/passwd,
\windows\system32, C:\evil, c:/evil, ..\..\windows,
foo\..\bar, empty string, NULL
If the predicate in task_decompress.c is ever edited, the copy
here must be updated to match -- this is noted in the test file's
header.
|
||
|
|
c37f540b72 |
Core Info Cache Improvements
- Core info cache can now be enabled/disabled on all platforms via a new `Settings > Core > Cache Core Info Files` option - Core info cache file has been renamed from `.cache` to `core_info.cache` (i.e. it is no longer a 'hidden' file on Unix platforms, so can be deleted easily) - The core info cache file is now compressed (rzip) to further reduce disk IO - The presence of a `core_info.refresh` file in the core info directory will force a one-time refresh of the info cache. This file is generated automatically when toggling on the `Cache Core Info Files` option, and we will also add it to core info file packaging such that updating info files (either manually or via the online updater) will force a refresh - The core info cache no longer contains 'core is locked' and 'firmware missing' data fields; these are 'dynamic' properties that must be determined at runtime - The 'core is locked' status is now determined on core info intialisation by parsing the core directory listing, rather than by performing indivdual 'lock file exists' checks. This minimises file IO, and greatly improves performance on devices with slow storage - While parsing the core info cache file, we now avoid unnecessary `strdup()`s when adding entries to the resultant cache list - Memory leaks (potential and real) have been fixed, and safety checks added - Build errors have been fixed |
||
|
|
ba1ed2da4b |
New faster json parser/writer library rjson
Replace existing libraries jsonsax_full and jsonsax with it |
||
|
|
772ed15d88 | Remove file_path_str from Salamander | ||
|
|
df8f93e9d5 | Split up VFS file_path code into separate file | ||
|
|
278dc75285 | (tasks) Fix samples | ||
|
|
6154371692 | use threads for command-line scanner if available | ||
|
|
f9a8c617ce | add pthread for command-line scanner when platform is not windows | ||
|
|
6c1310ca06 | (database task standalone) Add HAVE_THREADS | ||
|
|
388a931f95 | improve Makefile for standalone scanner | ||
|
|
98b7a21c42 | (task database standalone) Add 7zip and zlib support | ||
|
|
1a1418ccfb | (Samples) tasks/database - should work again | ||
|
|
28ff4b391a | Clean up white space. | ||
|
|
5697a24946 | (samples/tasks/database) Define -DHAVE_COMPRESSION | ||
|
|
25ab35b7d0 | (samples/tasks/database) Add hardcoded extensions for OSX | ||
|
|
639152e291 |
Hardcode exts for now - we assume this standalone program will
only be used on Windows/OSX/Linux systems for now |
||
|
|
c24179dc63 | Scanning should now work in standalone program | ||
|
|
d66b8fa882 | Start implementing main function | ||
|
|
00cc4ca7aa | (samples) Gets close to linking now | ||
|
|
7ccecc80c1 | Update samples/tasks/database | ||
|
|
908de15e7b | Start adding samples - not done yet |