Skip to content

Commit 3d0d50e

Browse files
committed
system/nxpkg: make locks instance-safe
Assisted-by: OpenAI Codex:gpt-5.6-sol Signed-off-by: aviralgarg05 <gargaviral99@gmail.com>
1 parent f31378e commit 3d0d50e

3 files changed

Lines changed: 83 additions & 122 deletions

File tree

system/nxpkg/pkg.h

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -55,26 +55,15 @@
5555
#define PKG_DESCRIPTION_MAX 127
5656
#define PKG_CATEGORY_MAX 31
5757
#define PKG_HASH_HEX_LEN 64
58-
/* Each manifest slot is ~1.7KB (dominated by PKG_LAUNCH_ARGS_MAX slots).
59-
* Keep the catalog bounded so repository-provided metadata cannot cause
60-
* unbounded memory use. Callers should allocate struct pkg_index_s from
61-
* the application heap rather than placing it on a small task stack.
62-
*/
58+
/* Keep repository metadata memory bounded. */
6359

6460
#define PKG_INDEX_MAX 16
6561
#define PKG_INSTALLED_MAX 16
6662
#define PKG_INSTALLED_VERSIONS_MAX 8
6763
#define PKG_LAUNCH_ARGS_MAX 8
6864
#define PKG_LAUNCH_ARG_MAX 127
6965

70-
/* Caps against a malicious/compromised HTTP server: without these, an
71-
* oversized response can exhaust SD-card space (downloads) or force an
72-
* unbounded single heap allocation sized directly off attacker-controlled
73-
* content (pkg_store_read_text). Text/metadata files (index.jsn,
74-
* instpkg.jsn) are always small; artifact downloads cover the largest
75-
* real payloads seen in practice (a multi-MB WAD, a ~1MB game ELF) with
76-
* generous headroom.
77-
*/
66+
/* Bound metadata allocations and artifact downloads. */
7867

7968
#define PKG_TEXT_MAX_SIZE (256 * 1024)
8069
#define PKG_DOWNLOAD_MAX_SIZE (32 * 1024 * 1024)
@@ -243,9 +232,9 @@ int pkg_resolve_artifact_source(FAR char *buffer, size_t size,
243232
FAR const struct pkg_manifest_s *manifest);
244233
int pkg_resolve_icon_source(FAR char *buffer, size_t size,
245234
FAR const struct pkg_manifest_s *manifest);
246-
int pkg_acquire_source(FAR const char *source, FAR const char *dest,
247-
FAR const char *renew_lock_path);
235+
int pkg_acquire_source(FAR const char *source, FAR const char *dest);
248236
int pkg_lock_create(FAR const char *path);
237+
void pkg_lock_remove(FAR const char *path);
249238
void pkg_reclaim_stale_lock(FAR const char *path);
250239
int pkg_sync(FAR const char *source);
251240
int pkg_install(FAR const char *name);

system/nxpkg/pkg_install.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,7 @@ int pkg_install(FAR const char *name)
433433
pkg_txn_clear_state(name);
434434
if (lock[0] != '\0')
435435
{
436-
pkg_store_remove_file(lock);
436+
pkg_lock_remove(lock);
437437
}
438438

439439
pkg_info("installed %s version %s", manifest->name, manifest->version);
@@ -451,7 +451,7 @@ int pkg_install(FAR const char *name)
451451
pkg_txn_clear_state(name);
452452
if (lock[0] != '\0')
453453
{
454-
pkg_store_remove_file(lock);
454+
pkg_lock_remove(lock);
455455
}
456456

457457
free(index);

system/nxpkg/pkg_store.c

Lines changed: 77 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,7 @@
2727
#include <dirent.h>
2828
#include <errno.h>
2929
#include <fcntl.h>
30-
#include <inttypes.h>
31-
#include <pthread.h>
3230
#include <signal.h>
33-
#include <stdint.h>
3431
#include <stdlib.h>
3532
#include <stdio.h>
3633
#include <string.h>
@@ -44,51 +41,30 @@
4441
* Pre-processor Definitions
4542
****************************************************************************/
4643

47-
#define PKG_LOCK_RECORD_MAGIC "NXPKG1"
48-
#define PKG_LOCK_RECORD_SIZE 64
49-
50-
/****************************************************************************
51-
* Private Data
52-
****************************************************************************/
53-
54-
static pthread_once_t g_pkg_lock_boot_once = PTHREAD_ONCE_INIT;
55-
static uint64_t g_pkg_lock_boot_id;
44+
#define PKG_LOCK_OWNER "owner"
5645

5746
/****************************************************************************
5847
* Private Functions
5948
****************************************************************************/
6049

61-
static void pkg_lock_init_boot_id(void)
62-
{
63-
arc4random_buf(&g_pkg_lock_boot_id, sizeof(g_pkg_lock_boot_id));
64-
if (g_pkg_lock_boot_id == 0)
65-
{
66-
g_pkg_lock_boot_id = 1;
67-
}
68-
}
69-
70-
static uint64_t pkg_lock_get_boot_id(void)
71-
{
72-
if (pthread_once(&g_pkg_lock_boot_once, pkg_lock_init_boot_id) != 0)
73-
{
74-
return 1;
75-
}
76-
77-
return g_pkg_lock_boot_id;
78-
}
79-
80-
static int pkg_lock_read_owner(FAR const char *path,
81-
FAR uint64_t *boot_id,
82-
FAR pid_t *owner)
50+
static int pkg_lock_read_owner(FAR const char *path, FAR pid_t *owner)
8351
{
84-
char record[PKG_LOCK_RECORD_SIZE];
85-
unsigned long long parsed_boot;
52+
char owner_path[PATH_MAX];
53+
char record[32];
54+
char extra;
8655
long parsed_owner;
8756
ssize_t nread;
8857
int fd;
8958
int ret;
9059

91-
fd = open(path, O_RDONLY);
60+
ret = snprintf(owner_path, sizeof(owner_path), "%s/%s", path,
61+
PKG_LOCK_OWNER);
62+
if (ret < 0 || (size_t)ret >= sizeof(owner_path))
63+
{
64+
return ret < 0 ? ret : -ENAMETOOLONG;
65+
}
66+
67+
fd = open(owner_path, O_RDONLY);
9268
if (fd < 0)
9369
{
9470
return -errno;
@@ -104,16 +80,13 @@ static int pkg_lock_read_owner(FAR const char *path,
10480

10581
close(fd);
10682
record[nread] = '\0';
107-
108-
ret = sscanf(record, PKG_LOCK_RECORD_MAGIC " %llx %ld",
109-
&parsed_boot, &parsed_owner);
110-
if (ret != 2 || parsed_owner <= 0 ||
83+
if (sscanf(record, "%ld%c", &parsed_owner, &extra) != 1 ||
84+
parsed_owner <= 0 ||
11185
(long)(pid_t)parsed_owner != parsed_owner)
11286
{
11387
return -EINVAL;
11488
}
11589

116-
*boot_id = (uint64_t)parsed_boot;
11790
*owner = (pid_t)parsed_owner;
11891
return 0;
11992
}
@@ -274,78 +247,104 @@ int pkg_store_prepare_layout(void)
274247

275248
int pkg_lock_create(FAR const char *path)
276249
{
277-
char record[PKG_LOCK_RECORD_SIZE];
250+
char owner_path[PATH_MAX];
251+
char record[32];
278252
int fd;
279253
int ret;
280254

281-
fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0644);
282-
if (fd < 0)
255+
if (mkdir(path, 0755) < 0)
283256
{
284257
return -errno;
285258
}
286259

287-
ret = snprintf(record, sizeof(record), PKG_LOCK_RECORD_MAGIC
288-
" %016" PRIx64 " %ld\n",
289-
pkg_lock_get_boot_id(), (long)getpid());
290-
if (ret < 0 || (size_t)ret >= sizeof(record))
260+
ret = snprintf(owner_path, sizeof(owner_path), "%s/%s", path,
261+
PKG_LOCK_OWNER);
262+
if (ret < 0 || (size_t)ret >= sizeof(owner_path))
291263
{
292264
ret = ret < 0 ? ret : -ENAMETOOLONG;
293-
goto errout;
265+
rmdir(path);
266+
return ret;
294267
}
295268

296-
ret = pkg_store_write_all(fd, record, (size_t)ret);
297-
if (ret < 0)
269+
fd = open(owner_path, O_WRONLY | O_CREAT | O_EXCL, 0644);
270+
if (fd < 0)
298271
{
299-
goto errout;
272+
ret = -errno;
273+
rmdir(path);
274+
return ret;
300275
}
301276

302-
if (fsync(fd) < 0)
277+
ret = snprintf(record, sizeof(record), "%ld", (long)getpid());
278+
if (ret < 0 || (size_t)ret >= sizeof(record))
279+
{
280+
ret = ret < 0 ? ret : -EOVERFLOW;
281+
}
282+
else
283+
{
284+
ret = pkg_store_write_all(fd, record, (size_t)ret);
285+
}
286+
287+
if (ret == 0 && fsync(fd) < 0)
303288
{
304289
ret = -errno;
305-
goto errout;
306290
}
307291

308-
if (close(fd) < 0)
292+
if (close(fd) < 0 && ret == 0)
309293
{
310294
ret = -errno;
311-
unlink(path);
312-
return ret;
313295
}
314296

315-
return 0;
297+
if (ret < 0)
298+
{
299+
unlink(owner_path);
300+
rmdir(path);
301+
}
316302

317-
errout:
318-
close(fd);
319-
unlink(path);
320303
return ret;
321304
}
322305

306+
void pkg_lock_remove(FAR const char *path)
307+
{
308+
char owner_path[PATH_MAX];
309+
int ret;
310+
311+
ret = snprintf(owner_path, sizeof(owner_path), "%s/%s", path,
312+
PKG_LOCK_OWNER);
313+
if (ret >= 0 && (size_t)ret < sizeof(owner_path))
314+
{
315+
unlink(owner_path);
316+
}
317+
318+
rmdir(path);
319+
}
320+
323321
void pkg_reclaim_stale_lock(FAR const char *path)
324322
{
325-
uint64_t boot_id;
326323
pid_t owner;
327324
int ret;
328325

329-
ret = pkg_lock_read_owner(path, &boot_id, &owner);
330-
if (ret == -EINVAL)
326+
ret = pkg_lock_read_owner(path, &owner);
327+
if (ret == -ENOENT || ret == -EINVAL)
331328
{
332-
/* The creator may have completed open(O_EXCL) without writing yet.
333-
* Give that window time to close before reading again.
334-
*/
329+
/* The creator may not have written the owner marker yet. */
335330

336331
usleep(20 * 1000);
337-
ret = pkg_lock_read_owner(path, &boot_id, &owner);
332+
ret = pkg_lock_read_owner(path, &owner);
333+
if (ret == -ENOENT || ret == -EINVAL)
334+
{
335+
pkg_lock_remove(path);
336+
return;
337+
}
338338
}
339339

340340
if (ret < 0)
341341
{
342342
return;
343343
}
344344

345-
if (boot_id != pkg_lock_get_boot_id())
345+
if (owner == getpid())
346346
{
347-
pkg_error("reclaiming lock from an earlier boot '%s'", path);
348-
unlink(path);
347+
pkg_lock_remove(path);
349348
return;
350349
}
351350

@@ -358,7 +357,7 @@ void pkg_reclaim_stale_lock(FAR const char *path)
358357
{
359358
pkg_error("reclaiming lock from exited task %ld '%s'",
360359
(long)owner, path);
361-
unlink(path);
360+
pkg_lock_remove(path);
362361
}
363362
}
364363

@@ -550,12 +549,7 @@ int pkg_store_read_text(FAR const char *path, FAR char **buffer)
550549
return -EINVAL;
551550
}
552551

553-
/* Reject anything unreasonably large before the size is trusted for an
554-
* allocation: guards both against a malicious/oversized text file (this
555-
* path is used for the network-fetched index.jsn) and against
556-
* "length + 1" wrapping if st_size were ever attacker-influenced up to
557-
* SIZE_MAX.
558-
*/
552+
/* Validate the size before allocating length plus its terminator. */
559553

560554
if (st.st_size < 0 || st.st_size > (off_t)PKG_TEXT_MAX_SIZE)
561555
{
@@ -616,10 +610,7 @@ int pkg_store_read_text(FAR const char *path, FAR char **buffer)
616610
* Name: pkg_store_make_tmp_path
617611
*
618612
* Description:
619-
* Derive a staging path for an atomic write/copy to "path", under a
620-
* short-name-compatible extension instead of appending ".tmp" (which
621-
* would produce a second '.' in the final path component and break on
622-
* FAT filesystems without long file name support).
613+
* Build a FAT short-name-compatible staging path.
623614
*
624615
****************************************************************************/
625616

@@ -711,14 +702,7 @@ int pkg_store_write_text_atomic(FAR const char *path, FAR const char *text)
711702
return ret;
712703
}
713704

714-
/* Force the write through to disk before renaming. nxpkg writes
715-
* several small, unrelated files back-to-back during install (per-
716-
* package txn state, then the shared installed-packages database);
717-
* without an explicit sync here, the FAT driver's single shared
718-
* sector cache can still hold a not-yet-committed buffer for this
719-
* file when the very next atomic write starts touching a different
720-
* file, corrupting one or both.
721-
*/
705+
/* Commit the temporary file before the atomic rename. */
722706

723707
ret = fsync(fd);
724708
if (ret < 0)
@@ -815,11 +799,7 @@ int pkg_store_copy_file(FAR const char *src, FAR const char *dest)
815799
close(infd);
816800

817801
#ifndef CONFIG_PSEUDOFS_FILE
818-
/* Force the payload through to disk before renaming - this is the
819-
* largest write in the whole install pipeline (WAD/game-ELF-sized
820-
* payloads), so a hard power-loss here is the scenario the atomic
821-
* temp+rename is specifically protecting against.
822-
*/
802+
/* Commit the payload before the atomic rename. */
823803

824804
if (fsync(outfd) < 0)
825805
{
@@ -875,15 +855,7 @@ int pkg_store_remove_version_dir(FAR const char *name,
875855
FAR struct dirent *ent;
876856
int ret;
877857

878-
/* Generic directory-content removal (rather than unlinking the payload
879-
* and manifest.jsn by their known names) so this same helper works both
880-
* to reclaim a partially staged version directory after a failed
881-
* install (pkg_install.c) and to prune/remove a fully-installed
882-
* version, without needing to already know that version's artifact
883-
* filename. Best-effort throughout: this runs from error/cleanup
884-
* paths where a still-failing removal shouldn't itself abort the
885-
* caller.
886-
*/
858+
/* Remove every file in a staged or installed version directory. */
887859

888860
ret = pkg_store_format_version_path(path, sizeof(path), name, version);
889861
if (ret < 0)

0 commit comments

Comments
 (0)