Skip to content

Commit cba3a9c

Browse files
committed
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.
1 parent 1fe435a commit cba3a9c

5 files changed

Lines changed: 439 additions & 4 deletions

File tree

.github/workflows/Linux-samples-tasks.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,27 @@ jobs:
126126
test -x video_shader_wildcard_test
127127
timeout 60 ./video_shader_wildcard_test
128128
echo "[pass] video_shader_wildcard_test"
129+
130+
- name: Build and run input_remap_bounds_test (ASan)
131+
shell: bash
132+
working-directory: samples/tasks/input_remap
133+
run: |
134+
set -eu
135+
# Regression test for the input-remap analog-axis OOB-
136+
# write fixes in configuration.c::input_remapping_load_file()
137+
# and input/input_driver.c. Pre-fix a malformed .rmp
138+
# could supply a remap target outside [0, RARCH_ANALOG_
139+
# BIND_LIST_END), which the use sites in input_driver.c
140+
# then indexed into a fixed-size analog_value[][8] array
141+
# without bounds (button -> analog branch) or with a
142+
# broken bound that compared elements against bytes
143+
# (analog -> analog branch using sizeof instead of
144+
# ARRAY_SIZE). Build under AddressSanitizer so any
145+
# reintroduction of either primitive is caught at the
146+
# bounds level. If configuration.c or input_driver.c
147+
# amends the relevant predicates, the verbatim copies
148+
# in input_remap_bounds_test.c must follow.
149+
make clean all SANITIZER=address
150+
test -x input_remap_bounds_test
151+
timeout 60 ./input_remap_bounds_test
152+
echo "[pass] input_remap_bounds_test"

configuration.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6984,6 +6984,21 @@ bool input_remapping_load_file(void *data, const char *path)
69846984
if (_remap == -1)
69856985
_remap = RARCH_UNMAPPED;
69866986

6987+
/* Reject .rmp values outside the legitimate range.
6988+
* Runtime use sites in input_driver.c index a
6989+
* fixed-size analog_value[][8] array using
6990+
* (_remap - RARCH_FIRST_CUSTOM_BIND); pre-this-patch
6991+
* a malformed .rmp could supply an arbitrary int
6992+
* here and OOB-write up to ~1007 elements past
6993+
* analog_value, corrupting adjacent input-state
6994+
* fields and (for the last user) the keys[] mask.
6995+
* Legal values are any concrete bind index in
6996+
* [0, RARCH_ANALOG_BIND_LIST_END) plus the
6997+
* RARCH_UNMAPPED sentinel. */
6998+
if ( _remap != (int)RARCH_UNMAPPED
6999+
&& (_remap < 0 || _remap >= (int)RARCH_ANALOG_BIND_LIST_END))
7000+
_remap = RARCH_UNMAPPED;
7001+
69877002
configuration_set_uint(settings,
69887003
settings->uints.input_remap_ids[i][j], _remap);
69897004
}
@@ -7012,6 +7027,12 @@ bool input_remapping_load_file(void *data, const char *path)
70127027
if (_remap == -1)
70137028
_remap = RARCH_UNMAPPED;
70147029

7030+
/* See the matching comment in the button-branch
7031+
* above: same OOB-write primitive, same fix. */
7032+
if ( _remap != (int)RARCH_UNMAPPED
7033+
&& (_remap < 0 || _remap >= (int)RARCH_ANALOG_BIND_LIST_END))
7034+
_remap = RARCH_UNMAPPED;
7035+
70157036
configuration_set_uint(settings,
70167037
settings->uints.input_remap_ids[i][j], _remap);
70177038
}

input/input_driver.c

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7384,13 +7384,26 @@ void input_driver_poll(void)
73847384
}
73857385
else
73867386
{
7387-
int invert = 1;
7387+
unsigned remap_axis_bind =
7388+
remap_button - RARCH_FIRST_CUSTOM_BIND;
7389+
int invert = 1;
7390+
7391+
/* Bound: analog_value is int16_t[8]. remap_button
7392+
* comes from settings->uints.input_remap_ids[][]
7393+
* which is loaded from .rmp config files;
7394+
* input_remapping_load_file's config_get_int
7395+
* accepts any integer, so a malformed .rmp can
7396+
* supply remap_button anywhere in (RARCH_FIRST_
7397+
* CUSTOM_BIND, INT_MAX], producing a write at
7398+
* arbitrary offset past analog_value[i]. Reject
7399+
* out-of-range targets here. */
7400+
if (remap_axis_bind >= ARRAY_SIZE(handle->analog_value[i]))
7401+
continue;
73887402

73897403
if (remap_button % 2 != 0)
73907404
invert = -1;
73917405

7392-
handle->analog_value[i][
7393-
remap_button - RARCH_FIRST_CUSTOM_BIND] =
7406+
handle->analog_value[i][remap_axis_bind] =
73947407
(p_new_state->analog_buttons[j]
73957408
? p_new_state->analog_buttons[j]
73967409
: 32767) * invert;
@@ -7424,7 +7437,11 @@ void input_driver_poll(void)
74247437
unsigned remap_axis_bind =
74257438
remap_axis - RARCH_FIRST_CUSTOM_BIND;
74267439

7427-
if (remap_axis_bind < sizeof(handle->analog_value[i]))
7440+
/* Pre-patch this read 'sizeof(handle->analog_value[i])'
7441+
* which is 16 (bytes), but the array has 8 elements;
7442+
* any remap_axis_bind in [8, 15] caused an OOB write.
7443+
* Use ARRAY_SIZE so the check is in elements. */
7444+
if (remap_axis_bind < ARRAY_SIZE(handle->analog_value[i]))
74287445
{
74297446
int invert = 1;
74307447
if ( (k % 2 == 0 && remap_axis % 2 != 0)

samples/tasks/input_remap/Makefile

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
TARGET := input_remap_bounds_test
2+
3+
SOURCES := input_remap_bounds_test.c
4+
5+
OBJS := $(SOURCES:.c=.o)
6+
7+
CFLAGS += -Wall -pedantic -std=gnu99 -g -O0
8+
9+
ifneq ($(SANITIZER),)
10+
CFLAGS := -fsanitize=$(SANITIZER) -fno-omit-frame-pointer $(CFLAGS)
11+
LDFLAGS := -fsanitize=$(SANITIZER) $(LDFLAGS)
12+
endif
13+
14+
all: $(TARGET)
15+
16+
%.o: %.c
17+
$(CC) -c -o $@ $< $(CFLAGS)
18+
19+
$(TARGET): $(OBJS)
20+
$(CC) -o $@ $^ $(LDFLAGS)
21+
22+
clean:
23+
rm -f $(TARGET) $(OBJS)
24+
25+
.PHONY: clean

0 commit comments

Comments
 (0)