fix: hardening part 2, guest sizes and indices in the OS layer and renderer #1422
No reviewers
Labels
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
question
testing wanted
wontfix
No milestone
No assignees
1 participant
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Hookshot!1422
Loading…
Reference in a new issue
No description provided.
Delete branch "security-hardening-2"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
A second pass after #1398. It checks that pass's fixes for ways around them, and covers the HLE services, the renderer's
copies and draws, and the files players share. Each commit is one topic.
Ways around the first pass
fix(fs): read guest paths once, within bounds; more Windows device names
host_pathcopies the guest path once, then checks and uses the copy. Another guest thread could change the pathbetween the check and the use.
gstrgives""for a path that does not end within 0x302 bytes of guest memory.guest_path_okalso refusesCOM¹-COM³andLPT¹-LPT³(UTF-8, the game's code page),CLOCK$, and a devicename followed by spaces (
NUL .txt).DeleteDirectoryRecursivelyon a folder that is itself a link removes only the link. The posix layer'sGetFileAttributesAmarks links as reparse points, as Windows does.tests/test_fs_links.py: new cases for the link attribute, andguest_path_okbuilt as Windows builds it(device names).
fix(nvn): check the whole of each array the game passes
P(i)checks one byte. The arrays copied with a size now usePN(i, n): border colours, viewports, vertex states,buffer ranges, render target lists, window textures, push constants, blend colour.
fix(nvn): bound ReadTexels and WriteTexels to the game's buffer
renderer read or wrote at
data + k * img + y * row: anywhere in the process.region objects are checked whole.
tests/test_nvn_host_tables.py: extents, strides that wrap, a stride past the window.fix(saves): also refuse Windows junctions on Python before 3.12
os.path.isjunctionexists from Python 3.12 only, andcopytreewalks into a junction as into a folder. A link is nowany reparse point or symbolic link, from
os.lstat.tests/test_oot_saves.py test_junction_refused(a junction simulated throughos.lstat). It fails on theold check.
fix(env): hookshot_env.txt names no libraries, files to write or shares
SDL_VULKAN_LIBRARY,SDL_VIDEO_EGL_DRIVER, ...) are left out. A bare file name islooked for beside the game and in the current folder on Windows.
_FILEor_DIR,RX_VK_FRAME_LOGandHOS_PRESENT_TRACEtake no value from this file. A bare nameresolves in the game folder:
HOS_SAMPLE_FILE=hookshot_vk.dllemptied the renderer.RX_VK_DLSS_DIRmay not be a share (\\server\x,//server/x)..and..count as paths.HOS_ENV_FILE's file still sets anything. The readme says so.tests/test_env_file.py, new lines for each case.fix(update): no file links in a downloaded Linux list; downloads read at most what they expect
file://part links are taken only from a list on this PC (the tools' own,HOOKSHOT_LINUX_LIST). In a downloadedlist they could read
/dev/zerountil memory ran out, or test whether a local file has a given hash.tests/test_linux_update.py: the remote case and the limit.HLE services
fix(video): check a decoder input's size and data
intforav_new_packet, then copied in whole: a heap overflow. A sizepast
INT_MAXis refused, and the data must lie in guest memory.fix(audio): bound memory pool handles, microphone rings, reverb delays and Opus buffers
ReleaseMemoryPoolwrote, and the getters read, past it.led to writes before the ring.
ReadInputStreamwrites only into guest memory.tests/test_audio_in.py: a stereo stream of 0x20000001 frames. The old code crashes on it.fix(fs): refuse a mount when the mount table is full
mounts[16]. It now fails with a mount error.fix(nvdrv): keep channel sync point ids inside the table
syncpt[192]unchecked. Ids are used againafter the last one.
fix(mem): check the guest ranges of heap and page calls
SetMemoryHeapSizeand svcSetHeapSizecheck the size without wrap-around. A large size passed the old check, and alater shrink zeroed memory past the window.
AllocateMemoryPagesandFreeMemoryPagesrefuse ranges outside the guest window. A wrapped range was zeroed whole.hos_commitanddiscard_pagesact on guest pages only. On Linux they change protection, or discard, whatever rangethey get.
fix(os): message queue storage, calendar on bad times, vsync event slots
NULL; it now stops with a message.ToCalendarTimeon a time the C library cannot convert returns zeros, not stack bytes.GetDisplayVsyncEventtakes its slot under a lock. Two threads could write past the 8 slots.fix(windows): load XInput and IMM32 from System32
LOAD_LIBRARY_SEARCH_SYSTEM32, as forvulkan-1.dll.Renderer
fix(vk): bound copies, buffer binds and indirect draws by what the game names
and past the guest window.
Before, a wrapped size checked one row while Vulkan copied many.
gpu_buffer_bind's fast path checks the size without wrap-around, aspool_ofdoes.fallback draws only the records it converted.
GOBs;
RX_VK_MESH_ARGSno longer divides by zero.fix(vk): check the stage and slot of each kept texture answer
hookshot_programs.binand the pipeline state list name a stage and a descriptor slot for each texture answer. Theywere used as indices unchecked, on every draw that found the record.
used.
tests/test_program_cache_cleanup.py: an answer past the stages and an answer past the slots, bothrefused, their modules released.
tests/test_program_answer_domains.pyextracts the new helper.Other files players share
fix(mods): do not walk a mod's romfs folder that is a link
/), and the indexwalked the whole disk.
tests/test_fs_links.py.fix(builder): refuse an update tag that names no folder of its own
builders\that the update replaces...survived thecleaning. An empty name, a leading dot, or more than 128 characters is now refused, as the setup and the Linux lists do.
ci: keep the release token off redirects
release_parts.getsendsGH_TOKENwithadd_unredirected_header. urllib copies plain headers to a redirect, whichmay go to another host.
How it was tested
python -I -B tests/run_all.py: 203 passed, 5 skipped (no tool for them here).hookshot_vk.so: passes, and the vk ABI check is clean.hookshot_vk.dllcross-built with llvm-mingw; a syntax check of every changed host file with llvm-mingw'sclang.
Not in this PR
scripts/linux/deps.shdownloads dav1d and the Vulkan, SPIR-V and VMA headers as tag archives without a hash (GitLab'sare not byte-stable). Pinning them by commit archive or release tarball needs a decision.
ubuntu:22.04by tag and the toolchain PPA'sg++-13unpinned; its libstdc++ is linkedstatically into the shipped renderer.
stamp matches. An install key in the stamp, or a MAC, would close that.
fonts/regular.ttfandbold.ttfbeside the game go to stb_truetype on Linux, which is not safe for untrustedfonts (a fuzzer finds out-of-bounds reads within a minute). The ImGui menu work replaces that path.
SetDefaultDllDirectories. Every load found here is now by full path or fromSystem32, but SDL's own loads still use the default order.
nx2/nup.pyextraction has no output cap per compressed member and no cycle check outside strict mode. It reads theplayer's own dump.
hookshot_log.txtincludes full paths (user names).