Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ Game pkgs (UP / EP / JP / HP / CUSA / PPSA / PCSA / etc.) work fine.
setup, permissions, and uploading from your phone.

**Q: Which PS5 firmware works?**
ps5upload is built against PS5 Payload SDK v0.38, which resolves
ps5upload is built against PS5 Payload SDK v0.40, which resolves
kernel offsets at startup for every firmware it knows about. The
same binary runs on the full range **1.00 – 12.70**.

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ make run-client # launch the Tauri dev app
(`libwebkit2gtk-4.1-dev`, `libgtk-3-dev`, `librsvg2-dev`,
`libayatana-appindicator3-dev`, `libxdo-dev`, `libssl-dev`,
`build-essential`), Node.js 22 LTS via NodeSource (only if missing),
Rust via rustup, and PS5 Payload SDK v0.38 → `~/ps5-payload-sdk`.
Rust via rustup, and PS5 Payload SDK v0.40 → `~/ps5-payload-sdk`.
- **`make install-macos`** — macOS: Xcode CLT, Homebrew, `node`, `llvm@18`
(the only Homebrew llvm shipped with `ld.lld` — required by
`prospero-clang`), Rust via rustup, and PS5 Payload SDK.
Expand Down Expand Up @@ -301,7 +301,7 @@ cross-platform, and live-PS5 validation workflow.
**PS5 payload** — every firmware the PS5 Payload SDK supports,
currently **1.00 through 12.70** on every console model (original
CFI-1xxx, Slim CFI-2xxx, Pro CFI-7xxx, Digital). Built against SDK
v0.38, which ships per-firmware kernel offsets and resolves them at
v0.40, which ships per-firmware kernel offsets and resolves them at
payload startup via `kernel_get_fw_version()` — the same binary
runs on every supported firmware without per-release rebuilds.

Expand Down
8 changes: 8 additions & 0 deletions client/src/state/pkgLibrary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1520,6 +1520,14 @@ const makePkgLibraryStore = () =>
status: "idle",
lastResult: { ok: false, message: mainErr || "Install was rejected." },
});
// Surface failures in the bell too (success already notifies above).
// Without this a failed item — an update or DLC especially — was silent
// if the user navigated away from the Library tab mid-install.
pushNotification(
stalled ? "warning" : "error",
`${label} install ${stalled ? "didn’t finish" : "failed"}`,
{ body: mainErr || "The PS5 didn’t confirm the install. Try again." },
);
}
} catch (e) {
patch({ status: "idle", lastResult: { ok: false, message: pkgError(e) } });
Expand Down
9 changes: 1 addition & 8 deletions engine/crates/ps5upload-engine/src/webui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,7 @@ fn asset_response(path: &str) -> Option<Response> {
.first_raw()
.unwrap_or("application/octet-stream");
let body: Vec<u8> = file.data.into_owned();
Some(
(
[(header::CONTENT_TYPE, mime)],
body,
)
.into_response(),
)
Some(([(header::CONTENT_TYPE, mime)], body).into_response())
}

/// Return a response for `path` inside the embedded bundle. If the path is
Expand Down Expand Up @@ -84,4 +78,3 @@ pub async fn spa_fallback(req: Request) -> Response {
// directly to e.g. `/library`.
spa_response("index.html")
}

39 changes: 35 additions & 4 deletions payload/src/ptrace_remote.c
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,14 @@ int pt_attach(pid_t pid) {
/* Block until the child reports the SIGSTOP. Without this the
* subsequent PT_GETREGS races the kernel and returns ESRCH. */
if (waitpid(pid, 0, 0) == -1) {
/* PT_ATTACH already succeeded: we are still the tracer and the
* target is stopped. Returning without detaching leaks a frozen
* SceShellUI and leaves the next pt_attach EBUSY under our own
* tracer. waitpid realistically only fails here on EINTR (one
* of our signal handlers firing mid-wait), so a clean detach
* restores the target to running and frees the slot. Best-effort
* — if detach also fails there is nothing left to try. */
(void)pt_detach(pid, 0);
return -1;
}
return 0;
Expand Down Expand Up @@ -312,14 +320,27 @@ long pt_syscall(pid_t pid, int sysno, ...) {
jmp_reg.r_r9 = va_arg(ap, uint64_t);
va_end(ap);

if (pt_setregs(pid, &jmp_reg) != 0) return -1;
if (pt_setregs(pid, &jmp_reg) != 0) {
/* Mirror pt_call: best-effort restore of the original regs in
* case the failed PT_SETREGS partially applied, and so a later
* PT_DETACH doesn't resume the target at the syscall site with
* junk argument registers. */
(void)pt_setregs(pid, &bak_reg);
return -1;
}

/* Single-step until we land back on the syscall site after
* sysret. Bound to 1k steps because syscalls are fast. */
int step_budget = 1000;
while (jmp_reg.r_rsp <= bak_reg.r_rsp && step_budget > 0) {
if (pt_step(pid) != 0) return -1;
if (pt_getregs(pid, &jmp_reg) != 0) return -1;
if (pt_step(pid) != 0) {
(void)pt_setregs(pid, &bak_reg);
return -1;
}
if (pt_getregs(pid, &jmp_reg) != 0) {
(void)pt_setregs(pid, &bak_reg);
return -1;
}
step_budget--;
}
if (step_budget == 0) {
Expand All @@ -333,7 +354,17 @@ long pt_syscall(pid_t pid, int sysno, ...) {

intptr_t pt_mmap(pid_t pid, intptr_t addr, size_t len, int prot,
int flags, int fd, off_t off) {
return pt_syscall(pid, SYS_mmap, addr, len, prot, flags, fd, off);
long r = pt_syscall(pid, SYS_mmap, addr, len, prot, flags, fd, off);
/* On syscall failure the kernel writes -errno into rax (e.g.
* -ENOMEM under memory pressure, -EINVAL, -EACCES). Every caller
* checks `scratch == -1 || scratch == 0`, so a -12 (0xFFFFFFFFFFFFFFF4)
* sails past as a "valid pointer" and the next pt_copyin/pt_call
* dereferences it inside SceShellUI → ShellUI crash, and Sony's
* watchdog can cascade into our payload. Collapse the whole errno
* range here so every caller is fixed in one place. Valid mmap
* results are page-aligned and >= 0x1000, never in [-4095,-1]. */
if ((unsigned long)r >= (unsigned long)-4095) return -1;
return r;
}

int pt_munmap(pid_t pid, intptr_t addr, size_t len) {
Expand Down
10 changes: 9 additions & 1 deletion payload/src/register.c
Original file line number Diff line number Diff line change
Expand Up @@ -515,7 +515,15 @@ static int copy_dir_recursive(const char *src, const char *dst) {
n = snprintf(dp, sizeof(dp), "%s/%s", dst, e->d_name);
if (n < 0 || (size_t)n >= sizeof(dp)) { rc = -1; break; }
struct stat st;
if (stat(sp, &st) != 0) { rc = -1; break; }
/* lstat, not stat: stat() follows a symlink and resolves it to
* the link *target*'s mode, so a symlink to a regular file
* (e.g. a hostile dump's sce_sys/leak -> /system_data/priv/mms/
* app.db) would match S_ISREG and get its target copied into
* user-accessible /user/app/<id>/sce_sys/, and a dangling
* symlink would abort the whole sce_sys copy. lstat() lets
* symlinks fall through to "skipped" as the comment below
* already documents as the intent. */
if (lstat(sp, &st) != 0) { rc = -1; break; }
if (S_ISDIR(st.st_mode)) {
if (copy_dir_recursive(sp, dp) != 0) { rc = -1; break; }
} else if (S_ISREG(st.st_mode)) {
Expand Down
17 changes: 14 additions & 3 deletions payload/src/runtime.c
Original file line number Diff line number Diff line change
Expand Up @@ -1388,7 +1388,7 @@ void runtime_reconcile_mounts(void) {
* the mount was created, keep the mount — users may have
* intentionally moved the file and we don't own cleanup of
* that. Log only; don't clean up. */
char src[256];
char src[512];
int have_src = mount_tracker_read(mnt_on, src, sizeof(src));
if (have_src) {
struct stat src_st;
Expand Down Expand Up @@ -8068,8 +8068,19 @@ static int handle_fs_mount(runtime_state_t *state, int client_fd,
* from the image filename. */
if (mount_name[0] == '\0') {
fs_mount_derive_name(image_path, mount_name, sizeof(mount_name));
} else if (strchr(mount_name, '/') != NULL ||
strstr(mount_name, "..") != NULL) {
}
/* Validate whether the name was caller-supplied or just derived
* from the image filename: reject path separators, "..", and a
* lone ".". The "." case matters because snprintf below then
* builds /mnt/ps5upload/. which the kernel normalises to
* FS_MOUNT_BASE itself — shadowing every existing mount in the
* namespace. That is the same footgun the strcmp(mount_point,
* FS_MOUNT_BASE) guard blocks for caller-supplied mount_points
* above, but this derived path bypasses it. fs_mount_derive_name
* can also yield "." for image names like "..exfat" or ".". */
if (strchr(mount_name, '/') != NULL ||
strstr(mount_name, "..") != NULL ||
strcmp(mount_name, ".") == 0) {
return send_frame(client_fd, FTX2_FRAME_ERROR, 0, trace_id,
"fs_mount_bad_name", 17);
}
Expand Down
4 changes: 2 additions & 2 deletions scripts/install-macos.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
# (llvm@18 is the only Homebrew llvm shipped with `ld.lld`, which prospero-clang needs;
# the root Makefile pins LLVM_CONFIG to llvm@18 on macOS — keep them aligned)
# - Rust toolchain (rustup, stable, default profile)
# - PS5 Payload SDK v0.38 → $PS5_PAYLOAD_SDK (default $HOME/ps5-payload-sdk)
# - PS5 Payload SDK v0.40 → $PS5_PAYLOAD_SDK (default $HOME/ps5-payload-sdk)
#
# After it finishes the script prints the env exports you need to add to ~/.zshrc
# (or ~/.bash_profile) so `make build` and `make run-client` work in any new shell.
Expand All @@ -23,7 +23,7 @@ set -euo pipefail
# it to /opt/ps5-payload-sdk (root-only). Override the install location with
# PS5_SDK_INSTALL_DIR if you want somewhere else.
SDK_DIR="${PS5_SDK_INSTALL_DIR:-$HOME/ps5-payload-sdk}"
SDK_TAG="v0.38"
SDK_TAG="v0.40"
SDK_URL="https://github.com/ps5-payload-dev/sdk/releases/download/${SDK_TAG}/ps5-payload-sdk.zip"

BREW_DEPS=(
Expand Down
4 changes: 2 additions & 2 deletions scripts/install-ubuntu.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
# libxdo, libssl, build-essential, file, curl, wget, unzip, pkg-config, python3)
# - Rust toolchain (rustup, stable, default profile)
# - Node.js 22 LTS via NodeSource (only if `node` is missing — keeps existing installs)
# - PS5 Payload SDK v0.38 → $PS5_PAYLOAD_SDK (default $HOME/ps5-payload-sdk)
# - PS5 Payload SDK v0.40 → $PS5_PAYLOAD_SDK (default $HOME/ps5-payload-sdk)
#
# After it finishes, the script prints the env exports you need to add to ~/.bashrc
# (or ~/.zshrc) so `make build` and `make run-client` work in any new shell.
Expand All @@ -22,7 +22,7 @@ set -euo pipefail
# it to /opt/ps5-payload-sdk (root-only). Override the install location with
# PS5_SDK_INSTALL_DIR if you want somewhere else.
SDK_DIR="${PS5_SDK_INSTALL_DIR:-$HOME/ps5-payload-sdk}"
SDK_TAG="v0.38"
SDK_TAG="v0.40"
SDK_URL="https://github.com/ps5-payload-dev/sdk/releases/download/${SDK_TAG}/ps5-payload-sdk.zip"
NODE_MAJOR="22"

Expand Down
4 changes: 2 additions & 2 deletions scripts/install-windows.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
# - Visual Studio 2022 Build Tools w/ C++ workload (MSVC + Windows SDK)
# - 7zip (used to extract the PS5 SDK zip in CI-clean way)
# - Microsoft Edge WebView2 Runtime (preinstalled on Win11; verified)
# - PS5 Payload SDK v0.38 → $env:USERPROFILE\ps5-payload-sdk
# - PS5 Payload SDK v0.40 → $env:USERPROFILE\ps5-payload-sdk
#
# Run from an elevated PowerShell:
# PS> Set-ExecutionPolicy -Scope Process Bypass -Force
Expand All @@ -27,7 +27,7 @@ param(
# *build-time* SDK path and may point at somewhere the current user can't
# write to. Pass -SdkDir or set $env:PS5_SDK_INSTALL_DIR to change.
[string]$SdkDir = $(if ($env:PS5_SDK_INSTALL_DIR) { $env:PS5_SDK_INSTALL_DIR } else { Join-Path $env:USERPROFILE 'ps5-payload-sdk' }),
[string]$SdkTag = 'v0.38'
[string]$SdkTag = 'v0.40'
)

$ErrorActionPreference = 'Stop'
Expand Down