mirror of
https://github.com/knutwurst/patchdl.git
synced 2026-10-06 08:00:21 +02:00
Concurrency: atomic g_stage/g_err, snapshot pkg_diag, safe open, SQLite mutex
g_stage and g_err are now _Atomic. The backend init thread publishes
stage transitions and function-pointer assignments; HTTP request
handlers read g_stage to decide whether to call the Sony API. With the
old `volatile int` reads, nothing in the C memory model ordered the
function-pointer loads against the stage check — a stage==5 sighting
could (in theory) come before the pointer stores were visible. Default
seq_cst on _Atomic gives us the acquire/release pairing for free.
/api/pkgdiag returned g_pkg_diag_json directly with RESPMEM_PERSISTENT,
so MHD's writer thread could read the buffer while record_pkg_diag was
mid-snprintf — torn JSON or a missing NUL terminator. Snapshot under
g_mutex into a heap copy and queue with RESPMEM_MUST_FREE instead.
patchdl_net.c gets fopen_safe(): open() with O_NOFOLLOW|O_CLOEXEC and
mode 0600, then fdopen. The old fopen("wb") follows symlinks (a
malicious symlink at dest_path could redirect the write) and creates
mode 0666 (libc default). Both download paths now use it.
patchdl_appdb opens SQLite with SQLITE_OPEN_FULLMUTEX. Today the scan
runs on the startup thread only, but a future rescan triggered from the
HTTP thread would otherwise race the handle.
This commit is contained in:
1 parent
a6d9f939b5
commit
fcb0a5c3b0
4 files changed
+43
-9
No files matched your search
+5
-1
@@ -61,8 +61,12 @@ patchdl_appdb_load(patchdl_appinfo_t **out, size_t *count) {
|
|||||||
*out = NULL;
|
*out = NULL;
|
||||||
*count = 0;
|
*count = 0;
|
||||||
|
|
||||||
|
/* FULLMUTEX: today only the startup thread calls this, but future code
|
||||||
|
paths (a manual rescan triggered from the HTTP thread) would otherwise
|
||||||
|
race the SQLite handle. Cost is one mutex per call. */
|
||||||
if (sqlite3_open_v2(APP_DB_URI, &db,
|
if (sqlite3_open_v2(APP_DB_URI, &db,
|
||||||
SQLITE_OPEN_READONLY | SQLITE_OPEN_URI, NULL) != SQLITE_OK) {
|
SQLITE_OPEN_READONLY | SQLITE_OPEN_URI |
|
||||||
|
SQLITE_OPEN_FULLMUTEX, NULL) != SQLITE_OK) {
|
||||||
if (db) sqlite3_close(db);
|
if (db) sqlite3_close(db);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <stdatomic.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@@ -112,9 +113,12 @@ static ai_get_status_fn ai_get_status;
|
|||||||
symbols via the kernel dynlib helpers. Runs in a detached thread; the HTTP
|
symbols via the kernel dynlib helpers. Runs in a detached thread; the HTTP
|
||||||
handler reports the stage and never blocks.
|
handler reports the stage and never blocks.
|
||||||
stage: 0 idle, 1 resolve loader, 2 load modules, 3 resolve symbols,
|
stage: 0 idle, 1 resolve loader, 2 load modules, 3 resolve symbols,
|
||||||
4 initialize, 5 ready, negative = failure at that step. */
|
4 initialize, 5 ready, negative = failure at that step.
|
||||||
static volatile int g_stage;
|
Stored as _Atomic so the worker's release-store and the request handlers'
|
||||||
static int g_err;
|
acquire-loads pair properly — the function pointers they read after
|
||||||
|
stage==5 must not be reordered ahead of the stage check. */
|
||||||
|
static _Atomic int g_stage;
|
||||||
|
static _Atomic int g_err;
|
||||||
static pthread_mutex_t g_mtx = PTHREAD_MUTEX_INITIALIZER;
|
static pthread_mutex_t g_mtx = PTHREAD_MUTEX_INITIALIZER;
|
||||||
static char g_probe_json[2048]; /* filled by the backend thread */
|
static char g_probe_json[2048]; /* filled by the backend thread */
|
||||||
static char g_last_content_id[AI_CONTENTID_SIZE];
|
static char g_last_content_id[AI_CONTENTID_SIZE];
|
||||||
|
|||||||
+20
-3
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@@ -9,6 +10,7 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <strings.h>
|
#include <strings.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
@@ -33,6 +35,21 @@
|
|||||||
#define PATCHDL_BUF_MAX_VERXML (16 * 1024 * 1024)
|
#define PATCHDL_BUF_MAX_VERXML (16 * 1024 * 1024)
|
||||||
#define PATCHDL_BUF_MAX_MANIFEST (64 * 1024 * 1024)
|
#define PATCHDL_BUF_MAX_MANIFEST (64 * 1024 * 1024)
|
||||||
|
|
||||||
|
#ifdef PATCHDL_HAVE_CURL
|
||||||
|
/* Replacement for fopen("wb"/"r+b") that refuses to follow a symlink at the
|
||||||
|
destination (would let a malicious symlink redirect the download) and pins
|
||||||
|
the new file's mode to 0600. Returns NULL on any open error. */
|
||||||
|
static FILE *
|
||||||
|
fopen_safe(const char *path, int rw_existing) {
|
||||||
|
int flags = O_CLOEXEC | O_NOFOLLOW;
|
||||||
|
int fd;
|
||||||
|
flags |= rw_existing ? O_RDWR : (O_WRONLY | O_CREAT | O_TRUNC);
|
||||||
|
fd = open(path, flags, 0600);
|
||||||
|
if (fd < 0) return NULL;
|
||||||
|
return fdopen(fd, rw_existing ? "r+b" : "wb");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef PATCHDL_HAVE_CURL
|
#ifdef PATCHDL_HAVE_CURL
|
||||||
|
|
||||||
static const char *ALLOWED_HOSTS[] = {
|
static const char *ALLOWED_HOSTS[] = {
|
||||||
@@ -486,7 +503,7 @@ int
|
|||||||
patchdl_http_download_progress(const char *url, const char *dest_path,
|
patchdl_http_download_progress(const char *url, const char *dest_path,
|
||||||
long long *bytes_out,
|
long long *bytes_out,
|
||||||
patchdl_download_progress_cb cb, void *ctx) {
|
patchdl_download_progress_cb cb, void *ctx) {
|
||||||
FILE *fp = fopen(dest_path, "wb");
|
FILE *fp = fopen_safe(dest_path, 0);
|
||||||
progress_state_t progress = { cb, ctx, 0, 0 };
|
progress_state_t progress = { cb, ctx, 0, 0 };
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
@@ -628,10 +645,10 @@ patchdl_http_download_manifest_progress(const char *manifest_url,
|
|||||||
written continues mid-piece via an HTTP byte range (with a fall back to
|
written continues mid-piece via an HTTP byte range (with a fall back to
|
||||||
re-fetching it whole if the CDN ignores the range). */
|
re-fetching it whole if the CDN ignores the range). */
|
||||||
if (resume) {
|
if (resume) {
|
||||||
fp = fopen(dest_path, "r+b");
|
fp = fopen_safe(dest_path, 1);
|
||||||
if (fp) { fseek(fp, 0, SEEK_END); have = ftell(fp); if (have < 0) have = 0; }
|
if (fp) { fseek(fp, 0, SEEK_END); have = ftell(fp); if (have < 0) have = 0; }
|
||||||
}
|
}
|
||||||
if (!fp) { fp = fopen(dest_path, "wb"); have = 0; }
|
if (!fp) { fp = fopen_safe(dest_path, 0); have = 0; }
|
||||||
if (!fp) { free(manifest.data); return -1; }
|
if (!fp) { free(manifest.data); return -1; }
|
||||||
started = (have <= 0);
|
started = (have <= 0);
|
||||||
|
|
||||||
|
|||||||
+11
-2
@@ -2013,8 +2013,17 @@ on_request(void *cls, struct MHD_Connection *conn, const char *url,
|
|||||||
return queue_json_owned(conn, MHD_HTTP_OK, strdup(p));
|
return queue_json_owned(conn, MHD_HTTP_OK, strdup(p));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!strcmp(url, "/api/pkgdiag"))
|
if (!strcmp(url, "/api/pkgdiag")) {
|
||||||
return queue_json(conn, MHD_HTTP_OK, g_pkg_diag_json);
|
/* Snapshot under the lock — otherwise MHD would read g_pkg_diag_json
|
||||||
|
in-place (RESPMEM_PERSISTENT) while record_pkg_diag is mid-snprintf,
|
||||||
|
producing a torn read or a missing NUL terminator. */
|
||||||
|
char snap[sizeof(g_pkg_diag_json)];
|
||||||
|
pthread_mutex_lock(&g_mutex);
|
||||||
|
memcpy(snap, g_pkg_diag_json, sizeof(snap));
|
||||||
|
pthread_mutex_unlock(&g_mutex);
|
||||||
|
snap[sizeof(snap) - 1] = '\0';
|
||||||
|
return queue_json_owned(conn, MHD_HTTP_OK, strdup(snap));
|
||||||
|
}
|
||||||
|
|
||||||
/* Read-only diagnostic: re-fetch the patch manifest for a title (PatchDL can
|
/* Read-only diagnostic: re-fetch the patch manifest for a title (PatchDL can
|
||||||
bypass the DNS block) and dump each piece's offset/size/SHA-256 so the
|
bypass the DNS block) and dump each piece's offset/size/SHA-256 so the
|
||||||
|
|||||||
Reference in new issue
Block a user