Skip to content

Commit 6484ed9

Browse files
committed
rework unpacking of packages and harden package file format requirements
A crafted .apk file could to trick apk writing unverified data to an unexpected file during temporary file creation due to bugs in handling long link target name and the way a regular file is extracted. Several hardening steps are implemented to avoid this: - the temporary file is now always first unlinked (apk thus reserved all filenames .apk.* to be it's working files) - the temporary file is after that created with O_EXCL to avoid races - the temporary file is no longer directly the archive entry name and thus directly controlled by potentially untrusted data - long file names and link target names are now rejected - hard link targets are now more rigorously checked - various additional checks added for the extraction process to error out early in case of malformed (or old legacy) file Reported-by: Max Justicz <max@justi.cz>
1 parent b11f9aa commit 6484ed9

File tree

6 files changed

+142
-105
lines changed

6 files changed

+142
-105
lines changed

src/apk_archive.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ int apk_tar_write_entry(struct apk_ostream *, const struct apk_file_info *ae,
2828
int apk_tar_write_padding(struct apk_ostream *, const struct apk_file_info *ae);
2929

3030
int apk_archive_entry_extract(int atfd, const struct apk_file_info *ae,
31-
const char *suffix, struct apk_istream *is,
31+
const char *extract_name, const char *hardlink_name,
32+
struct apk_istream *is,
3233
apk_progress_cb cb, void *cb_ctx);
3334

3435
#endif

src/archive.c

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,12 @@ int apk_tar_parse(struct apk_istream *is, apk_archive_entry_parser parser,
317317
break;
318318
}
319319

320+
if (strnlen(entry.name, PATH_MAX) >= PATH_MAX-10 ||
321+
(entry.link_target && strnlen(entry.link_target, PATH_MAX) >= PATH_MAX-10)) {
322+
r = -ENAMETOOLONG;
323+
goto err;
324+
}
325+
320326
teis.bytes_left = entry.size;
321327
if (entry.mode & S_IFMT) {
322328
/* callback parser function */
@@ -428,23 +434,15 @@ int apk_tar_write_padding(struct apk_ostream *os, const struct apk_file_info *ae
428434
}
429435

430436
int apk_archive_entry_extract(int atfd, const struct apk_file_info *ae,
431-
const char *suffix, struct apk_istream *is,
437+
const char *extract_name, const char *link_target,
438+
struct apk_istream *is,
432439
apk_progress_cb cb, void *cb_ctx)
433440
{
434441
struct apk_xattr *xattr;
435-
char *fn = ae->name;
442+
const char *fn = extract_name ?: ae->name;
436443
int fd, r = -1, atflags = 0, ret = 0;
437444

438-
if (suffix != NULL) {
439-
fn = alloca(PATH_MAX);
440-
snprintf(fn, PATH_MAX, "%s%s", ae->name, suffix);
441-
}
442-
443-
if ((!S_ISDIR(ae->mode) && !S_ISREG(ae->mode)) ||
444-
(ae->link_target != NULL)) {
445-
/* non-standard entries need to be deleted first */
446-
unlinkat(atfd, fn, 0);
447-
}
445+
if (unlinkat(atfd, fn, 0) != 0 && errno != ENOENT) return -errno;
448446

449447
switch (ae->mode & S_IFMT) {
450448
case S_IFDIR:
@@ -454,7 +452,7 @@ int apk_archive_entry_extract(int atfd, const struct apk_file_info *ae,
454452
break;
455453
case S_IFREG:
456454
if (ae->link_target == NULL) {
457-
int flags = O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC;
455+
int flags = O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC | O_EXCL;
458456

459457
fd = openat(atfd, fn, flags, ae->mode & 07777);
460458
if (fd < 0) {
@@ -465,18 +463,12 @@ int apk_archive_entry_extract(int atfd, const struct apk_file_info *ae,
465463
if (r != ae->size) ret = r < 0 ? r : -ENOSPC;
466464
close(fd);
467465
} else {
468-
char *link_target = ae->link_target;
469-
if (suffix != NULL) {
470-
link_target = alloca(PATH_MAX);
471-
snprintf(link_target, PATH_MAX, "%s%s",
472-
ae->link_target, suffix);
473-
}
474-
r = linkat(atfd, link_target, atfd, fn, 0);
466+
r = linkat(atfd, link_target ?: ae->link_target, atfd, fn, 0);
475467
if (r < 0) ret = -errno;
476468
}
477469
break;
478470
case S_IFLNK:
479-
r = symlinkat(ae->link_target, atfd, fn);
471+
r = symlinkat(link_target ?: ae->link_target, atfd, fn);
480472
if (r < 0) ret = -errno;
481473
atflags |= AT_SYMLINK_NOFOLLOW;
482474
break;

src/commit.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,8 @@ static int run_commit_hook(void *ctx, int dirfd, const char *file)
232232
struct apk_database *db = hook->db;
233233
char fn[PATH_MAX], *argv[] = { fn, (char *) commit_hook_str[hook->type], NULL };
234234

235-
if ((apk_flags & (APK_NO_SCRIPTS | APK_SIMULATE)) != 0)
236-
return 0;
235+
if (file[0] == '.') return 0;
236+
if ((apk_flags & (APK_NO_SCRIPTS | APK_SIMULATE)) != 0) return 0;
237237

238238
snprintf(fn, sizeof(fn), "etc/apk/commit_hooks.d" "/%s", file);
239239
if ((apk_flags & APK_NO_COMMIT_HOOKS) != 0) {

src/database.c

Lines changed: 107 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -828,8 +828,9 @@ int apk_db_index_read(struct apk_database *db, struct apk_bstream *bs, int repo)
828828
case 'F':
829829
if (diri) apk_db_dir_apply_diri_permissions(diri);
830830
if (pkg->name == NULL) goto bad_entry;
831-
diri = apk_db_diri_new(db, pkg, l, &diri_node);
832-
file_diri_node = &diri->owned_files.first;
831+
diri = find_diri(ipkg, l, NULL, &diri_node);
832+
if (!diri) diri = apk_db_diri_new(db, pkg, l, &diri_node);
833+
file_diri_node = hlist_tail_ptr(&diri->owned_files);
833834
break;
834835
case 'a':
835836
if (file == NULL) goto bad_entry;
@@ -2358,6 +2359,31 @@ static struct apk_db_dir_instance *apk_db_install_directory_entry(struct install
23582359
return diri;
23592360
}
23602361

2362+
#define TMPNAME_MAX (PATH_MAX + 64)
2363+
2364+
static const char *format_tmpname(struct apk_package *pkg, struct apk_db_file *f, char tmpname[static TMPNAME_MAX])
2365+
{
2366+
EVP_MD_CTX mdctx;
2367+
unsigned char md[EVP_MAX_MD_SIZE];
2368+
apk_blob_t b = APK_BLOB_PTR_LEN(tmpname, TMPNAME_MAX);
2369+
2370+
if (!f) return NULL;
2371+
2372+
EVP_DigestInit(&mdctx, EVP_sha256());
2373+
EVP_DigestUpdate(&mdctx, pkg->name->name, strlen(pkg->name->name) + 1);
2374+
EVP_DigestUpdate(&mdctx, f->diri->dir->name, f->diri->dir->namelen);
2375+
EVP_DigestUpdate(&mdctx, "/", 1);
2376+
EVP_DigestUpdate(&mdctx, f->name, f->namelen);
2377+
EVP_DigestFinal(&mdctx, md, NULL);
2378+
2379+
apk_blob_push_blob(&b, APK_BLOB_PTR_LEN(f->diri->dir->name, f->diri->dir->namelen));
2380+
apk_blob_push_blob(&b, APK_BLOB_STR("/.apk."));
2381+
apk_blob_push_hexdump(&b, APK_BLOB_PTR_LEN((char *)md, 24));
2382+
apk_blob_push_blob(&b, APK_BLOB_PTR_LEN("", 1));
2383+
2384+
return tmpname;
2385+
}
2386+
23612387
static int apk_db_install_archive_entry(void *_ctx,
23622388
const struct apk_file_info *ae,
23632389
struct apk_istream *is)
@@ -2369,8 +2395,9 @@ static int apk_db_install_archive_entry(void *_ctx,
23692395
struct apk_installed_package *ipkg = pkg->ipkg;
23702396
apk_blob_t name = APK_BLOB_STR(ae->name), bdir, bfile;
23712397
struct apk_db_dir_instance *diri = ctx->diri;
2372-
struct apk_db_file *file;
2398+
struct apk_db_file *file, *link_target_file = NULL;
23732399
int ret = 0, r, type = APK_SCRIPT_INVALID;
2400+
char tmpname_file[TMPNAME_MAX], tmpname_link_target[TMPNAME_MAX];
23742401

23752402
r = apk_sign_ctx_process_file(&ctx->sctx, ae, is);
23762403
if (r <= 0)
@@ -2437,6 +2464,40 @@ static int apk_db_install_archive_entry(void *_ctx,
24372464
diri = apk_db_install_directory_entry(ctx, bdir);
24382465
}
24392466

2467+
/* Check hard link target to exist in this package */
2468+
if (S_ISREG(ae->mode) && ae->link_target) {
2469+
do {
2470+
struct apk_db_file *lfile;
2471+
struct apk_db_dir_instance *ldiri;
2472+
struct hlist_node *n;
2473+
apk_blob_t hldir, hlfile;
2474+
2475+
if (!apk_blob_rsplit(APK_BLOB_STR(ae->link_target),
2476+
'/', &hldir, &hlfile))
2477+
break;
2478+
2479+
ldiri = find_diri(ipkg, hldir, diri, NULL);
2480+
if (ldiri == NULL)
2481+
break;
2482+
2483+
hlist_for_each_entry(lfile, n, &ldiri->owned_files,
2484+
diri_files_list) {
2485+
if (apk_blob_compare(APK_BLOB_PTR_LEN(lfile->name, lfile->namelen),
2486+
hlfile) == 0) {
2487+
link_target_file = lfile;
2488+
break;
2489+
}
2490+
}
2491+
} while (0);
2492+
2493+
if (!link_target_file) {
2494+
apk_error(PKG_VER_FMT": "BLOB_FMT": no hard link target (%s) in archive",
2495+
PKG_VER_PRINTF(pkg), BLOB_PRINTF(name), ae->link_target);
2496+
ipkg->broken_files = 1;
2497+
return 0;
2498+
}
2499+
}
2500+
24402501
opkg = NULL;
24412502
file = apk_db_file_query(db, bdir, bfile);
24422503
if (file != NULL) {
@@ -2495,41 +2556,21 @@ static int apk_db_install_archive_entry(void *_ctx,
24952556
if (apk_verbosity >= 3)
24962557
apk_message("%s", ae->name);
24972558

2498-
/* Extract the file as name.apk-new */
2559+
/* Extract the file with temporary name */
24992560
file->acl = apk_db_acl_atomize(ae->mode, ae->uid, ae->gid, &ae->xattr_csum);
2500-
r = apk_archive_entry_extract(db->root_fd, ae, ".apk-new", is,
2501-
extract_cb, ctx);
2561+
r = apk_archive_entry_extract(
2562+
db->root_fd, ae,
2563+
format_tmpname(pkg, file, tmpname_file),
2564+
format_tmpname(pkg, link_target_file, tmpname_link_target),
2565+
is, extract_cb, ctx);
25022566

25032567
switch (r) {
25042568
case 0:
25052569
/* Hardlinks need special care for checksum */
2506-
if (ae->csum.type == APK_CHECKSUM_NONE &&
2507-
ae->link_target != NULL) {
2508-
do {
2509-
struct apk_db_file *lfile;
2510-
struct apk_db_dir_instance *ldiri;
2511-
struct hlist_node *n;
2512-
2513-
if (!apk_blob_rsplit(APK_BLOB_STR(ae->link_target),
2514-
'/', &bdir, &bfile))
2515-
break;
2516-
2517-
ldiri = find_diri(ipkg, bdir, diri, NULL);
2518-
if (ldiri == NULL)
2519-
break;
2520-
2521-
hlist_for_each_entry(lfile, n, &ldiri->owned_files,
2522-
diri_files_list) {
2523-
if (apk_blob_compare(APK_BLOB_PTR_LEN(lfile->name, lfile->namelen),
2524-
bfile) == 0) {
2525-
memcpy(&file->csum, &lfile->csum,
2526-
sizeof(file->csum));
2527-
break;
2528-
}
2529-
}
2530-
} while (0);
2531-
} else
2532-
memcpy(&file->csum, &ae->csum, sizeof(file->csum));
2570+
if (link_target_file)
2571+
memcpy(&file->csum, &link_target_file->csum, sizeof file->csum);
2572+
else
2573+
memcpy(&file->csum, &ae->csum, sizeof file->csum);
25332574
break;
25342575
case -ENOTSUP:
25352576
ipkg->broken_xattr = 1;
@@ -2547,8 +2588,11 @@ static int apk_db_install_archive_entry(void *_ctx,
25472588
if (name.ptr[name.len-1] == '/')
25482589
name.len--;
25492590

2550-
diri = apk_db_install_directory_entry(ctx, name);
2551-
apk_db_dir_prepare(db, diri->dir, ae->mode);
2591+
diri = ctx->diri = find_diri(ipkg, name, NULL, &ctx->file_diri_node);
2592+
if (!diri) {
2593+
diri = apk_db_install_directory_entry(ctx, name);
2594+
apk_db_dir_prepare(db, diri->dir, ae->mode);
2595+
}
25522596
apk_db_diri_set(diri, apk_db_acl_atomize(ae->mode, ae->uid, ae->gid, &ae->xattr_csum));
25532597
}
25542598
ctx->installed_size += ctx->current_file_size;
@@ -2558,25 +2602,24 @@ static int apk_db_install_archive_entry(void *_ctx,
25582602

25592603
static void apk_db_purge_pkg(struct apk_database *db,
25602604
struct apk_installed_package *ipkg,
2561-
const char *exten)
2605+
int is_installed)
25622606
{
25632607
struct apk_db_dir_instance *diri;
25642608
struct apk_db_file *file;
25652609
struct apk_db_file_hash_key key;
25662610
struct apk_file_info fi;
25672611
struct hlist_node *dc, *dn, *fc, *fn;
25682612
unsigned long hash;
2569-
char name[PATH_MAX];
2613+
char name[TMPNAME_MAX];
25702614

25712615
hlist_for_each_entry_safe(diri, dc, dn, &ipkg->owned_dirs, pkg_dirs_list) {
2572-
if (exten == NULL)
2573-
diri->dir->modified = 1;
2616+
if (is_installed) diri->dir->modified = 1;
25742617

25752618
hlist_for_each_entry_safe(file, fc, fn, &diri->owned_files, diri_files_list) {
2576-
snprintf(name, sizeof(name),
2577-
DIR_FILE_FMT "%s",
2578-
DIR_FILE_PRINTF(diri->dir, file),
2579-
exten ?: "");
2619+
if (is_installed)
2620+
snprintf(name, sizeof name, DIR_FILE_FMT, DIR_FILE_PRINTF(diri->dir, file));
2621+
else
2622+
format_tmpname(ipkg->pkg, file, name);
25802623

25812624
key = (struct apk_db_file_hash_key) {
25822625
.dirname = APK_BLOB_PTR_LEN(diri->dir->name, diri->dir->namelen),
@@ -2592,7 +2635,7 @@ static void apk_db_purge_pkg(struct apk_database *db,
25922635
if (apk_verbosity >= 3)
25932636
apk_message("%s", name);
25942637
__hlist_del(fc, &diri->owned_files.first);
2595-
if (exten == NULL) {
2638+
if (is_installed) {
25962639
apk_hash_delete_hashed(&db->installed.files, APK_BLOB_BUF(&key), hash);
25972640
db->installed.stats.files--;
25982641
}
@@ -2613,18 +2656,16 @@ static void apk_db_migrate_files(struct apk_database *db,
26132656
struct apk_file_info fi;
26142657
struct hlist_node *dc, *dn, *fc, *fn;
26152658
unsigned long hash;
2616-
char name[PATH_MAX], tmpname[PATH_MAX];
2659+
char name[PATH_MAX], tmpname[TMPNAME_MAX];
26172660
int cstype, r;
26182661

26192662
hlist_for_each_entry_safe(diri, dc, dn, &ipkg->owned_dirs, pkg_dirs_list) {
26202663
dir = diri->dir;
26212664
dir->modified = 1;
26222665

26232666
hlist_for_each_entry_safe(file, fc, fn, &diri->owned_files, diri_files_list) {
2624-
snprintf(name, sizeof(name), DIR_FILE_FMT,
2625-
DIR_FILE_PRINTF(diri->dir, file));
2626-
snprintf(tmpname, sizeof(tmpname), DIR_FILE_FMT ".apk-new",
2627-
DIR_FILE_PRINTF(diri->dir, file));
2667+
snprintf(name, sizeof(name), DIR_FILE_FMT, DIR_FILE_PRINTF(diri->dir, file));
2668+
format_tmpname(ipkg->pkg, file, tmpname);
26282669

26292670
key = (struct apk_db_file_hash_key) {
26302671
.dirname = APK_BLOB_PTR_LEN(dir->name, dir->namelen),
@@ -2665,8 +2706,21 @@ static void apk_db_migrate_files(struct apk_database *db,
26652706
APK_FI_NOFOLLOW | file->csum.type, &fi);
26662707
if ((apk_flags & APK_CLEAN_PROTECTED) ||
26672708
(file->csum.type != APK_CHECKSUM_NONE &&
2668-
apk_checksum_compare(&file->csum, &fi.csum) == 0))
2709+
apk_checksum_compare(&file->csum, &fi.csum) == 0)) {
26692710
unlinkat(db->root_fd, tmpname, 0);
2711+
} else {
2712+
snprintf(name, sizeof name,
2713+
DIR_FILE_FMT ".apk-new",
2714+
DIR_FILE_PRINTF(diri->dir, file));
2715+
if (renameat(db->root_fd, tmpname,
2716+
db->root_fd, name) != 0) {
2717+
apk_error(PKG_VER_FMT": failed to rename %s to %s.",
2718+
PKG_VER_PRINTF(ipkg->pkg),
2719+
tmpname, name);
2720+
ipkg->broken_files = 1;
2721+
}
2722+
}
2723+
26702724
} else {
26712725
/* Overwrite the old file */
26722726
if (renameat(db->root_fd, tmpname,
@@ -2799,7 +2853,7 @@ int apk_db_install_pkg(struct apk_database *db, struct apk_package *oldpkg,
27992853
if (ipkg == NULL)
28002854
goto ret_r;
28012855
apk_ipkg_run_script(ipkg, db, APK_SCRIPT_PRE_DEINSTALL, script_args);
2802-
apk_db_purge_pkg(db, ipkg, NULL);
2856+
apk_db_purge_pkg(db, ipkg, TRUE);
28032857
apk_ipkg_run_script(ipkg, db, APK_SCRIPT_POST_DEINSTALL, script_args);
28042858
apk_pkg_uninstall(db, oldpkg);
28052859
goto ret_r;
@@ -2822,15 +2876,15 @@ int apk_db_install_pkg(struct apk_database *db, struct apk_package *oldpkg,
28222876
cb, cb_ctx, script_args);
28232877
if (r != 0) {
28242878
if (oldpkg != newpkg)
2825-
apk_db_purge_pkg(db, ipkg, ".apk-new");
2879+
apk_db_purge_pkg(db, ipkg, FALSE);
28262880
apk_pkg_uninstall(db, newpkg);
28272881
goto ret_r;
28282882
}
28292883
apk_db_migrate_files(db, ipkg);
28302884
}
28312885

28322886
if (oldpkg != NULL && oldpkg != newpkg && oldpkg->ipkg != NULL) {
2833-
apk_db_purge_pkg(db, oldpkg->ipkg, NULL);
2887+
apk_db_purge_pkg(db, oldpkg->ipkg, TRUE);
28342888
apk_pkg_uninstall(db, oldpkg);
28352889
}
28362890

0 commit comments

Comments
 (0)