From 5387da457339676b34ce18b05f9696df5e3e02d9 Mon Sep 17 00:00:00 2001 From: Francisco Javier Trujillo Mata Date: Sat, 31 Oct 2026 16:58:23 +0100 Subject: [PATCH 1/3] ps2link: Bound IOP exception packet name within its storage Use an explicit, ABI-compatible wire packet struct that reserves the command header and a full 128-byte module name. Keep the EE offsets unchanged, cap and terminate names, and validate packet layout and boundary cases in portable C under CI. --- .github/workflows/compilation.yml | 11 +++++- iop/Makefile | 2 + iop/excepHandler.c | 32 ++++------------ iop/exception_packet.h | 28 ++++++++++++++ tests/exception_packet.c | 62 +++++++++++++++++++++++++++++++ 5 files changed, 110 insertions(+), 25 deletions(-) create mode 100644 iop/exception_packet.h create mode 100644 tests/exception_packet.c diff --git a/.github/workflows/compilation.yml b/.github/workflows/compilation.yml index 77345a8..e7dcf51 100644 --- a/.github/workflows/compilation.yml +++ b/.github/workflows/compilation.yml @@ -12,6 +12,15 @@ on: workflow_dispatch: {} jobs: + exception-packet-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Validate IOP exception packet layout and bounds (plain C) + run: | + cc -std=c99 -Wall -Wextra -Werror tests/exception_packet.c -o /tmp/ps2link-exception-packet-test + /tmp/ps2link-exception-packet-test + build: runs-on: ubuntu-latest container: ghcr.io/ps2dev/ps2-packer:latest @@ -60,7 +69,7 @@ jobs: path: ee/ps2link.elf release: - needs: [build] + needs: [build, exception-packet-test] runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/master' steps: diff --git a/iop/Makefile b/iop/Makefile index f9ae139..c5564f1 100644 --- a/iop/Makefile +++ b/iop/Makefile @@ -36,3 +36,5 @@ clean: include $(PS2SDK)/Defs.make include $(PS2SDK)/samples/Makefile.iopglobal + +$(IOP_OBJS_DIR)excepHandler.o: exception_packet.h diff --git a/iop/excepHandler.c b/iop/excepHandler.c index f967062..cd3c866 100644 --- a/iop/excepHandler.c +++ b/iop/excepHandler.c @@ -9,10 +9,9 @@ #include "irx_imports.h" #include "hostlink.h" #include "cmdHandler.h" +#include "exception_packet.h" -#define BUFFER_SIZE sizeof(exception_frame_t) + 4 + 4 + 128 - -u32 excep_buffer[BUFFER_SIZE / 4] __attribute__((aligned(16))); +static pko_iop_exception_packet_t excep_buffer __attribute__((aligned(16))); // taken from smod by mrbrown, only use one function, didn't wanna include another irx. @@ -70,8 +69,6 @@ static void excep_handler2(exception_frame_t *frame) { u32 r_epc; // relative epc char *module_name; - u32 len; - u32 *buffer = excep_buffer; module_name = ExceptionGetModuleName(frame->epc, &r_epc); @@ -79,28 +76,15 @@ static void excep_handler2(exception_frame_t *frame) if (module_name == NULL) { module_name = "unknown"; r_epc = frame->epc; - len = sizeof("unknown") - 1; - } else { - len = 0; - while (len < 127 && module_name[len] != '\0') - len++; } - // Leave room for the header, frame, metadata and terminating NUL. - - - buffer[0] = 0x0d02beba; // reverse engineering.. - buffer++; - - memcpy(buffer, frame, sizeof(*frame)); - buffer += (sizeof(exception_frame_t) / 4); - buffer[0] = r_epc; - buffer[1] = len; - buffer += 2; - memcpy(buffer, module_name, len); - ((char *)buffer)[len] = '\0'; + excep_buffer.command = 0x0d02beba; + memcpy(&excep_buffer.frame, frame, sizeof(*frame)); + excep_buffer.relative_epc = r_epc; + excep_buffer.name_length = + pko_copy_exception_name(excep_buffer.module_name, module_name); - pkoSendSifCmd(PKO_RPC_IOPEXCEP, excep_buffer, BUFFER_SIZE); + pkoSendSifCmd(PKO_RPC_IOPEXCEP, &excep_buffer, sizeof(excep_buffer)); } diff --git a/iop/exception_packet.h b/iop/exception_packet.h new file mode 100644 index 0000000..4f795fd --- /dev/null +++ b/iop/exception_packet.h @@ -0,0 +1,28 @@ +#ifndef PS2LINK_EXCEPTION_PACKET_H +#define PS2LINK_EXCEPTION_PACKET_H + +/* The EE receiver expects the module name at word 43 of the SIF packet. */ +#define PKO_IOP_EXCEPTION_NAME_BYTES 128 + +typedef struct { + u32 command; + exception_frame_t frame; + u32 relative_epc; + u32 name_length; + char module_name[PKO_IOP_EXCEPTION_NAME_BYTES]; +} pko_iop_exception_packet_t; + +/* Always leave a NUL inside the fixed-size packet name field. */ +static unsigned int pko_copy_exception_name(char *dest, const char *src) +{ + unsigned int len = 0; + + while (len < PKO_IOP_EXCEPTION_NAME_BYTES - 1 && src[len] != '\0') { + dest[len] = src[len]; + len++; + } + dest[len] = '\0'; + return len; +} + +#endif diff --git a/tests/exception_packet.c b/tests/exception_packet.c new file mode 100644 index 0000000..898c896 --- /dev/null +++ b/tests/exception_packet.c @@ -0,0 +1,62 @@ +#include +#include +#include +#include + +typedef uint32_t u32; +typedef struct { + u32 epc, cause, badvaddr, sr, regs[32], hi, lo, dcic; +} exception_frame_t; + +#include "../iop/exception_packet.h" + +static int check_name(const char *src, unsigned int expected_length) +{ + struct { + unsigned char before; + char name[PKO_IOP_EXCEPTION_NAME_BYTES]; + unsigned char after; + } data; + unsigned int length; + + memset(&data, 0xa5, sizeof(data)); + length = pko_copy_exception_name(data.name, src); + return length == expected_length && + data.name[length] == '\0' && + data.before == 0xa5 && data.after == 0xa5 && + memcmp(data.name, src, length) == 0; +} + +int main(void) +{ + char long_name[256]; + unsigned int i; + + for (i = 0; i < sizeof(long_name) - 1; i++) + long_name[i] = 'X'; + long_name[sizeof(long_name) - 1] = '\0'; + + /* The EE receiver uses the existing wire offsets, including word 43. */ + if (sizeof(exception_frame_t) != 156 || + offsetof(pko_iop_exception_packet_t, frame) != 4 || + offsetof(pko_iop_exception_packet_t, relative_epc) != 160 || + offsetof(pko_iop_exception_packet_t, name_length) != 164 || + offsetof(pko_iop_exception_packet_t, module_name) != 168 || + sizeof(pko_iop_exception_packet_t) != 296) { + fputs("FAIL IOP exception wire layout\n", stderr); + return 1; + } + + if (!check_name("", 0) || !check_name("unknown", 7) || + !check_name(long_name, 127)) { + fputs("FAIL IOP exception name bounds\n", stderr); + return 1; + } + long_name[123] = '\0'; + if (!check_name(long_name, 123)) { + fputs("FAIL boundary-length module name\n", stderr); + return 1; + } + puts("PASS exception packet layout and bounded module names"); + return 0; +} From 1e3e1f549022e940c311e9ffc645036bf8305f77 Mon Sep 17 00:00:00 2001 From: Francisco Javier Trujillo Mata Date: Sat, 31 Oct 2026 17:02:03 +0100 Subject: [PATCH 2/3] ps2link: Make exception name source limits explicit --- iop/excepHandler.c | 9 +++++++-- iop/exception_packet.h | 13 +++++++++---- tests/exception_packet.c | 8 ++++---- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/iop/excepHandler.c b/iop/excepHandler.c index cd3c866..85124bc 100644 --- a/iop/excepHandler.c +++ b/iop/excepHandler.c @@ -76,13 +76,18 @@ static void excep_handler2(exception_frame_t *frame) if (module_name == NULL) { module_name = "unknown"; r_epc = frame->epc; + excep_buffer.name_length = + pko_copy_exception_name(&excep_buffer, module_name, + sizeof("unknown") - 1); + } else { + excep_buffer.name_length = + pko_copy_exception_name(&excep_buffer, module_name, + PKO_IOP_EXCEPTION_NAME_BYTES - 1); } excep_buffer.command = 0x0d02beba; memcpy(&excep_buffer.frame, frame, sizeof(*frame)); excep_buffer.relative_epc = r_epc; - excep_buffer.name_length = - pko_copy_exception_name(excep_buffer.module_name, module_name); pkoSendSifCmd(PKO_RPC_IOPEXCEP, &excep_buffer, sizeof(excep_buffer)); } diff --git a/iop/exception_packet.h b/iop/exception_packet.h index 4f795fd..4d2c51b 100644 --- a/iop/exception_packet.h +++ b/iop/exception_packet.h @@ -13,15 +13,20 @@ typedef struct { } pko_iop_exception_packet_t; /* Always leave a NUL inside the fixed-size packet name field. */ -static unsigned int pko_copy_exception_name(char *dest, const char *src) +static unsigned int pko_copy_exception_name(pko_iop_exception_packet_t *packet, + const char *src, + unsigned int source_limit) { unsigned int len = 0; - while (len < PKO_IOP_EXCEPTION_NAME_BYTES - 1 && src[len] != '\0') { - dest[len] = src[len]; + if (source_limit >= PKO_IOP_EXCEPTION_NAME_BYTES) + source_limit = PKO_IOP_EXCEPTION_NAME_BYTES - 1; + + while (len < source_limit && src[len] != '\0') { + packet->module_name[len] = src[len]; len++; } - dest[len] = '\0'; + packet->module_name[len] = '\0'; return len; } diff --git a/tests/exception_packet.c b/tests/exception_packet.c index 898c896..e41e32a 100644 --- a/tests/exception_packet.c +++ b/tests/exception_packet.c @@ -14,17 +14,17 @@ static int check_name(const char *src, unsigned int expected_length) { struct { unsigned char before; - char name[PKO_IOP_EXCEPTION_NAME_BYTES]; + pko_iop_exception_packet_t packet; unsigned char after; } data; unsigned int length; memset(&data, 0xa5, sizeof(data)); - length = pko_copy_exception_name(data.name, src); + length = pko_copy_exception_name(&data.packet, src, strlen(src)); return length == expected_length && - data.name[length] == '\0' && + data.packet.module_name[length] == '\0' && data.before == 0xa5 && data.after == 0xa5 && - memcmp(data.name, src, length) == 0; + memcmp(data.packet.module_name, src, length) == 0; } int main(void) From 880ba38df7f58093007b50e4c708e8b39820bf24 Mon Sep 17 00:00:00 2001 From: Francisco Javier Trujillo Mata Date: Sat, 31 Oct 2026 17:03:07 +0100 Subject: [PATCH 3/3] ps2link: Pass bounded test fixture lengths explicitly --- tests/exception_packet.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/exception_packet.c b/tests/exception_packet.c index e41e32a..e548f39 100644 --- a/tests/exception_packet.c +++ b/tests/exception_packet.c @@ -10,7 +10,8 @@ typedef struct { #include "../iop/exception_packet.h" -static int check_name(const char *src, unsigned int expected_length) +static int check_name(const char *src, unsigned int source_length, + unsigned int expected_length) { struct { unsigned char before; @@ -20,7 +21,7 @@ static int check_name(const char *src, unsigned int expected_length) unsigned int length; memset(&data, 0xa5, sizeof(data)); - length = pko_copy_exception_name(&data.packet, src, strlen(src)); + length = pko_copy_exception_name(&data.packet, src, source_length); return length == expected_length && data.packet.module_name[length] == '\0' && data.before == 0xa5 && data.after == 0xa5 && @@ -47,13 +48,13 @@ int main(void) return 1; } - if (!check_name("", 0) || !check_name("unknown", 7) || - !check_name(long_name, 127)) { + if (!check_name("", 0, 0) || !check_name("unknown", 7, 7) || + !check_name(long_name, sizeof(long_name) - 1, 127)) { fputs("FAIL IOP exception name bounds\n", stderr); return 1; } long_name[123] = '\0'; - if (!check_name(long_name, 123)) { + if (!check_name(long_name, 123, 123)) { fputs("FAIL boundary-length module name\n", stderr); return 1; }