diff options
author | Timo Teräs <timo.teras@iki.fi> | 2018-09-05 19:49:22 +0300 |
---|---|---|
committer | Timo Teräs <timo.teras@iki.fi> | 2018-09-10 10:59:39 +0300 |
commit | 6484ed9849f03971eb48ee1fdc21a2f128247eb1 (patch) | |
tree | ed0ecf3a027f0497596355ae7895112c5cb99a4a /src/archive.c | |
parent | b11f9aa9286320a73a02cd14bfff5974e05a430b (diff) |
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>
Diffstat (limited to 'src/archive.c')
-rw-r--r-- | src/archive.c | 34 |
1 files changed, 13 insertions, 21 deletions
diff --git a/src/archive.c b/src/archive.c index bc36ce7..9a184fd 100644 --- a/src/archive.c +++ b/src/archive.c @@ -317,6 +317,12 @@ int apk_tar_parse(struct apk_istream *is, apk_archive_entry_parser parser, break; } + if (strnlen(entry.name, PATH_MAX) >= PATH_MAX-10 || + (entry.link_target && strnlen(entry.link_target, PATH_MAX) >= PATH_MAX-10)) { + r = -ENAMETOOLONG; + goto err; + } + teis.bytes_left = entry.size; if (entry.mode & S_IFMT) { /* callback parser function */ @@ -428,23 +434,15 @@ int apk_tar_write_padding(struct apk_ostream *os, const struct apk_file_info *ae } int apk_archive_entry_extract(int atfd, const struct apk_file_info *ae, - const char *suffix, struct apk_istream *is, + const char *extract_name, const char *link_target, + struct apk_istream *is, apk_progress_cb cb, void *cb_ctx) { struct apk_xattr *xattr; - char *fn = ae->name; + const char *fn = extract_name ?: ae->name; int fd, r = -1, atflags = 0, ret = 0; - if (suffix != NULL) { - fn = alloca(PATH_MAX); - snprintf(fn, PATH_MAX, "%s%s", ae->name, suffix); - } - - if ((!S_ISDIR(ae->mode) && !S_ISREG(ae->mode)) || - (ae->link_target != NULL)) { - /* non-standard entries need to be deleted first */ - unlinkat(atfd, fn, 0); - } + if (unlinkat(atfd, fn, 0) != 0 && errno != ENOENT) return -errno; switch (ae->mode & S_IFMT) { case S_IFDIR: @@ -454,7 +452,7 @@ int apk_archive_entry_extract(int atfd, const struct apk_file_info *ae, break; case S_IFREG: if (ae->link_target == NULL) { - int flags = O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC; + int flags = O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC | O_EXCL; fd = openat(atfd, fn, flags, ae->mode & 07777); if (fd < 0) { @@ -465,18 +463,12 @@ int apk_archive_entry_extract(int atfd, const struct apk_file_info *ae, if (r != ae->size) ret = r < 0 ? r : -ENOSPC; close(fd); } else { - char *link_target = ae->link_target; - if (suffix != NULL) { - link_target = alloca(PATH_MAX); - snprintf(link_target, PATH_MAX, "%s%s", - ae->link_target, suffix); - } - r = linkat(atfd, link_target, atfd, fn, 0); + r = linkat(atfd, link_target ?: ae->link_target, atfd, fn, 0); if (r < 0) ret = -errno; } break; case S_IFLNK: - r = symlinkat(ae->link_target, atfd, fn); + r = symlinkat(link_target ?: ae->link_target, atfd, fn); if (r < 0) ret = -errno; atflags |= AT_SYMLINK_NOFOLLOW; break; |