Skip to content

Commit 96ec45a

Browse files
committed
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.
1 parent b569dd0 commit 96ec45a

4 files changed

Lines changed: 278 additions & 15 deletions

File tree

samples/tasks/database/Makefile

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,11 @@ SOURCES_C := \
9595
$(CORE_DIR)/database_info.c \
9696
$(CORE_DIR)/core_info.c \
9797
$(CORE_DIR)/msg_hash.c \
98-
$(CORE_DIR)/intl/msg_hash_us.c \
9998
$(CORE_DIR)/playlist.c \
99+
$(CORE_DIR)/manual_content_scan.c \
100100
$(CORE_DIR)/verbosity.c \
101+
$(LIBRETRO_COMM_DIR)/formats/m3u/m3u_file.c \
102+
$(LIBRETRO_COMM_DIR)/formats/logiqx_dat/logiqx_dat.c \
101103
$(CORE_DIR)/libretro-db/bintree.c \
102104
$(CORE_DIR)/libretro-db/libretrodb.c \
103105
$(CORE_DIR)/libretro-db/query.c \
@@ -108,22 +110,29 @@ SOURCES_C := \
108110
$(LIBRETRO_COMM_DIR)/file/file_path.c \
109111
$(LIBRETRO_COMM_DIR)/file/file_path_io.c \
110112
$(LIBRETRO_COMM_DIR)/file/retro_dirent.c \
111-
$(LIBRETRO_COMM_DIR)/hash/rhash.c \
113+
$(LIBRETRO_COMM_DIR)/hash/lrc_hash.c \
112114
$(LIBRETRO_COMM_DIR)/compat/compat_fnmatch.c \
113115
$(LIBRETRO_COMM_DIR)/compat/compat_posix_string.c \
114116
$(LIBRETRO_COMM_DIR)/compat/compat_strcasestr.c \
115117
$(LIBRETRO_COMM_DIR)/compat/compat_strl.c \
116118
$(LIBRETRO_COMM_DIR)/compat/fopen_utf8.c \
119+
$(DEPS_DIR)/yxml/yxml.c \
117120
$(LIBRETRO_COMM_DIR)/formats/json/rjson.c \
121+
$(LIBRETRO_COMM_DIR)/formats/xml/rxml.c \
118122
$(LIBRETRO_COMM_DIR)/encodings/encoding_crc32.c \
119123
$(LIBRETRO_COMM_DIR)/encodings/encoding_utf.c \
124+
$(LIBRETRO_COMM_DIR)/features/features_cpu.c \
120125
$(LIBRETRO_COMM_DIR)/queues/task_queue.c \
121126
$(LIBRETRO_COMM_DIR)/lists/dir_list.c \
122127
$(LIBRETRO_COMM_DIR)/lists/string_list.c \
123128
$(LIBRETRO_COMM_DIR)/streams/interface_stream.c \
124129
$(LIBRETRO_COMM_DIR)/streams/memory_stream.c \
125130
$(LIBRETRO_COMM_DIR)/streams/file_stream.c \
131+
$(LIBRETRO_COMM_DIR)/streams/rzip_stream.c \
132+
$(LIBRETRO_COMM_DIR)/streams/trans_stream.c \
133+
$(LIBRETRO_COMM_DIR)/streams/trans_stream_pipe.c \
126134
$(LIBRETRO_COMM_DIR)/string/stdstring.c \
135+
$(LIBRETRO_COMM_DIR)/time/rtime.c \
127136
$(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.c
128137

129138
DEFINES = -DHAVE_LIBRETRODB -DHAVE_COMPRESSION
@@ -134,6 +143,7 @@ SOURCES_C += \
134143
$(LIBRETRO_COMM_DIR)/streams/trans_stream_zlib.c
135144
DEFINES += -DHAVE_ZLIB
136145
LIBS += -lz
146+
LIBS += -lm
137147
endif
138148

139149
ifeq ($(HAVE_7ZIP), 1)
@@ -142,20 +152,23 @@ SOURCES_C += \
142152
DEFINES += -DHAVE_7ZIP -D_7ZIP_ST
143153
INCDIRS += -I$(DEPS_DIR)
144154

145-
SOURCES_C += $(DEPS_DIR)/7zip/7zIn.c \
146-
$(DEPS_DIR)/7zip/Bra86.c \
147-
$(DEPS_DIR)/7zip/7zFile.c \
148-
$(DEPS_DIR)/7zip/7zStream.c \
149-
$(DEPS_DIR)/7zip/LzFind.c \
150-
$(DEPS_DIR)/7zip/LzmaDec.c \
151-
$(DEPS_DIR)/7zip/LzmaEnc.c \
155+
SOURCES_C += $(DEPS_DIR)/7zip/7zArcIn.c \
156+
$(DEPS_DIR)/7zip/7zBuf.c \
157+
$(DEPS_DIR)/7zip/7zCrc.c \
152158
$(DEPS_DIR)/7zip/7zCrcOpt.c \
153-
$(DEPS_DIR)/7zip/Bra.c \
154159
$(DEPS_DIR)/7zip/7zDec.c \
160+
$(DEPS_DIR)/7zip/7zFile.c \
161+
$(DEPS_DIR)/7zip/7zStream.c \
155162
$(DEPS_DIR)/7zip/Bcj2.c \
156-
$(DEPS_DIR)/7zip/7zCrc.c \
163+
$(DEPS_DIR)/7zip/Bra.c \
164+
$(DEPS_DIR)/7zip/Bra86.c \
165+
$(DEPS_DIR)/7zip/BraIA64.c \
166+
$(DEPS_DIR)/7zip/CpuArch.c \
167+
$(DEPS_DIR)/7zip/Delta.c \
168+
$(DEPS_DIR)/7zip/LzFind.c \
157169
$(DEPS_DIR)/7zip/Lzma2Dec.c \
158-
$(DEPS_DIR)/7zip/7zBuf.c
170+
$(DEPS_DIR)/7zip/LzmaDec.c \
171+
$(DEPS_DIR)/7zip/LzmaEnc.c
159172
endif
160173

161174
ifeq ($(HAVE_THREADS), 1)

samples/tasks/database/main.c

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,91 @@
99

1010
static bool loop_active = true;
1111

12-
static void main_msg_queue_push(const char *msg,
12+
/* Stubs for symbols referenced by the retroarch-tree sources we pull
13+
* in. The real definitions live in intl/msg_hash_us.c and
14+
* configuration.c, but those files transitively require RARCH_INTERNAL
15+
* which drags in the entire frontend subsystem. This sample only
16+
* exercises task_push_dbscan; none of these symbols are actually
17+
* invoked on the path through task_push_dbscan / task_queue_check.
18+
*
19+
* The `msg` parameter is declared as int rather than
20+
* `enum msg_hash_enums` because that enum lives in msg_hash.h, which
21+
* is not on the include path for this sample. At the link level the
22+
* signatures match since enum values promote to int. */
23+
int msg_hash_get_help_us_enum(int msg, char *s, size_t len)
24+
{
25+
(void)msg;
26+
if (s && len)
27+
s[0] = '\0';
28+
return 0;
29+
}
30+
31+
const char *msg_hash_to_str_us(int msg)
32+
{
33+
(void)msg;
34+
return "";
35+
}
36+
37+
void *config_get_ptr(void)
38+
{
39+
/* core_info_current_supports_savestate_level dereferences the
40+
* return as a settings_t*. That code path is never exercised
41+
* during a dbscan, but returning NULL would SEGV if it ever were.
42+
* A single static zeroed buffer is enough to keep dereferences
43+
* from crashing for any read -- the sample doesn't write to it. */
44+
static long long zeros[1024]; /* ~8 KiB, covers settings_t */
45+
return zeros;
46+
}
47+
48+
/* Additional stubs for retroarch-core symbols referenced transitively.
49+
* None of these are exercised on the dbscan path; they're link-time
50+
* stubs to avoid pulling in retroarch.c, runloop.c, frontend drivers,
51+
* and the UI/video subsystems. */
52+
void runloop_msg_queue_push(const char *msg, size_t len,
53+
unsigned prio, unsigned duration,
54+
bool flush, char *title, unsigned icon, unsigned category)
55+
{
56+
(void)msg; (void)len; (void)prio; (void)duration;
57+
(void)flush; (void)title; (void)icon; (void)category;
58+
}
59+
60+
bool retroarch_override_setting_is_set(unsigned enum_idx, void *data)
61+
{
62+
(void)enum_idx; (void)data;
63+
return false;
64+
}
65+
66+
void ui_companion_driver_notify_refresh(void)
67+
{
68+
}
69+
70+
void video_display_server_set_window_progress(int progress, bool finished)
71+
{
72+
(void)progress; (void)finished;
73+
}
74+
75+
uint64_t frontend_driver_get_free_memory(void)
76+
{
77+
return 0;
78+
}
79+
80+
/* dir_list_new_special lives in retroarch.c, which we cannot link
81+
* without dragging in the world. manual_content_scan calls this to
82+
* walk the scan directory; returning NULL causes the scan to bail
83+
* without producing results, which is fine for a standalone demo. */
84+
void *dir_list_new_special(const char *input_dir, unsigned type,
85+
const char *filter, bool show_hidden_files)
86+
{
87+
(void)input_dir; (void)type; (void)filter; (void)show_hidden_files;
88+
return NULL;
89+
}
90+
91+
static void main_msg_queue_push(retro_task_t *task,
92+
const char *msg,
1393
unsigned prio, unsigned duration,
1494
bool flush)
1595
{
96+
(void)task;
1697
fprintf(stderr, "MSGQ: %s\n", msg);
1798
}
1899

@@ -23,8 +104,10 @@ static void main_msg_queue_push(const char *msg,
23104
* error exit: -1
24105
*/
25106

26-
static void main_db_cb(void *task_data, void *user_data, const char *err)
107+
static void main_db_cb(retro_task_t *task,
108+
void *task_data, void *user_data, const char *err)
27109
{
110+
(void)task;
28111
fprintf(stderr, "DB CB: %s\n", err);
29112
loop_active = false;
30113
}
@@ -66,7 +149,7 @@ int main(int argc, char *argv[])
66149
#else
67150
task_queue_init(false /* threaded enable */, main_msg_queue_push);
68151
#endif
69-
core_info_init_list(core_info_dir, core_dir, exts, true, false);
152+
core_info_init_list(core_info_dir, core_dir, exts, true, false, NULL);
70153

71154
task_push_dbscan(playlist_dir, db_dir, input_dir, true,
72155
true, main_db_cb);

samples/tasks/decompress/Makefile

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
TARGET := archive_name_safety_test
2+
3+
SOURCES := archive_name_safety_test.c
4+
5+
OBJS := $(SOURCES:.c=.o)
6+
7+
CFLAGS += -Wall -pedantic -std=gnu99 -g -O0
8+
9+
all: $(TARGET)
10+
11+
%.o: %.c
12+
$(CC) -c -o $@ $< $(CFLAGS)
13+
14+
$(TARGET): $(OBJS)
15+
$(CC) -o $@ $^ $(LDFLAGS)
16+
17+
clean:
18+
rm -f $(TARGET) $(OBJS)
19+
20+
.PHONY: clean
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
/* Copyright (C) 2010-2026 The RetroArch team
2+
*
3+
* ---------------------------------------------------------------------------------------
4+
* The following license statement only applies to this file (archive_name_safety_test.c).
5+
* ---------------------------------------------------------------------------------------
6+
*
7+
* Permission is hereby granted, free of charge,
8+
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
9+
* to deal in the Software without restriction, including without limitation the rights to
10+
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
11+
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
14+
*
15+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
16+
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18+
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19+
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21+
*/
22+
23+
/* Regression for commit 87f2d0b (Zip Slip in tasks/task_decompress).
24+
*
25+
* The predicate archive_name_is_safe() is static inside
26+
* tasks/task_decompress.c and isn't in a public header, so this test
27+
* keeps its own verbatim copy of the predicate as the oracle. The
28+
* test's value is in locking in the predicate's contract -- any
29+
* future edit to the real predicate that changes semantics will
30+
* drift from this oracle and the test's maintainer must decide
31+
* whether the drift is intended.
32+
*
33+
* If you update archive_name_is_safe() in task_decompress.c, update
34+
* the copy in this file to match.
35+
*
36+
* Build standalone:
37+
* cc -Wall -std=gnu99 -g -O0 -o archive_name_safety_test \
38+
* archive_name_safety_test.c
39+
* ./archive_name_safety_test
40+
*/
41+
42+
#include <stdio.h>
43+
#include <stddef.h>
44+
#include <stdbool.h>
45+
46+
/* =============== verbatim from tasks/task_decompress.c =============== */
47+
static bool archive_name_is_safe(const char *name)
48+
{
49+
const char *p;
50+
const char *seg_start;
51+
52+
if (!name || !*name)
53+
return false;
54+
55+
/* Reject absolute paths in either Unix or Windows form. */
56+
if (name[0] == '/' || name[0] == '\\')
57+
return false;
58+
/* Reject drive-letter prefix e.g. "C:..." */
59+
if (name[1] == ':')
60+
return false;
61+
62+
/* Reject any ".." path segment. Treat both '/' and '\\' as
63+
* separators so Windows-authored archives can't traverse when
64+
* extracted on a POSIX host. */
65+
p = seg_start = name;
66+
for (;;)
67+
{
68+
char c = *p;
69+
if (c == '/' || c == '\\' || c == '\0')
70+
{
71+
if ((size_t)(p - seg_start) == 2
72+
&& seg_start[0] == '.' && seg_start[1] == '.')
73+
return false;
74+
if (c == '\0')
75+
break;
76+
seg_start = p + 1;
77+
}
78+
p++;
79+
}
80+
return true;
81+
}
82+
/* =============== end verbatim copy =============== */
83+
84+
static int failures = 0;
85+
86+
static void expect(const char *name, bool want)
87+
{
88+
bool got = archive_name_is_safe(name);
89+
if (got != want)
90+
{
91+
printf("[FAILED] %-32s expected %s, got %s\n",
92+
name ? name : "(null)",
93+
want ? "safe" : "UNSAFE",
94+
got ? "safe" : "UNSAFE");
95+
failures++;
96+
return;
97+
}
98+
printf("[SUCCESS] %-32s %s\n",
99+
name ? name : "(null)",
100+
want ? "safe" : "correctly rejected");
101+
}
102+
103+
int main(void)
104+
{
105+
/* --- legitimate archive contents -- must be accepted --- */
106+
expect("file.txt", true);
107+
expect("dir/file.txt", true);
108+
expect("dir/subdir/file.txt", true);
109+
expect("a.b/c", true);
110+
expect("...", true); /* three dots, not a segment of '..' */
111+
expect("..foo", true); /* prefix only */
112+
expect("foo..", true); /* suffix only */
113+
expect("x/y.ext", true);
114+
115+
/* --- zip-slip payloads -- must be rejected --- */
116+
expect("..", false);
117+
expect("../etc/passwd", false);
118+
expect("../../etc/passwd", false);
119+
expect("foo/../../etc/passwd", false);
120+
expect("foo/..", false);
121+
expect("foo/..//bar", false); /* double slash after traversal */
122+
expect("foo/../bar", false);
123+
124+
/* --- absolute paths -- must be rejected --- */
125+
expect("/etc/passwd", false);
126+
expect("\\windows\\system32", false);
127+
128+
/* --- drive-letter prefixes -- must be rejected --- */
129+
expect("C:\\evil", false);
130+
expect("c:/evil", false);
131+
132+
/* --- Windows-style traversal -- must be rejected --- */
133+
expect("..\\..\\windows", false);
134+
expect("foo\\..\\bar", false);
135+
136+
/* --- empty / NULL -- must be rejected --- */
137+
expect("", false);
138+
expect(NULL, false);
139+
140+
if (failures)
141+
{
142+
printf("\n%d test(s) failed\n", failures);
143+
return 1;
144+
}
145+
printf("\nAll archive_name_is_safe regression tests passed.\n");
146+
return 0;
147+
}

0 commit comments

Comments
 (0)