X-Git-Url: https://wimlib.net/git/?p=wimlib;a=blobdiff_plain;f=src%2Fdentry.c;h=db333849584c8e8641b317a1d5db882f09258654;hp=01945b2a33062b80b8a53f1c830ab8441d30795e;hb=9f56f9f091155065173712c3f254945fd8224511;hpb=241ac7fd624b980f1bfc34d830e6e5dbec1110b8 diff --git a/src/dentry.c b/src/dentry.c index 01945b2a..db333849 100644 --- a/src/dentry.c +++ b/src/dentry.c @@ -3,7 +3,7 @@ */ /* - * Copyright (C) 2012, 2013, 2014, 2015 Eric Biggers + * Copyright (C) 2012-2016 Eric Biggers * * This file is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free @@ -92,10 +92,10 @@ struct wim_dentry_on_disk { le32 attributes; /* A value that specifies the security descriptor for this file or - * directory. If -1, the file or directory has no security descriptor. - * Otherwise, it is a 0-based index into the WIM image's table of - * security descriptors (see: `struct wim_security_data') */ - sle32 security_id; + * directory. If 0xFFFFFFFF, the file or directory has no security + * descriptor. Otherwise, it is a 0-based index into the WIM image's + * table of security descriptors (see: `struct wim_security_data') */ + le32 security_id; /* Offset, in bytes, from the start of the uncompressed metadata * resource of this directory's child directory entries, or 0 if this @@ -358,7 +358,8 @@ dentry_out_total_length(const struct wim_dentry *dentry) dentry->d_short_name_nbytes); len = ALIGN(len, 8); - len += ALIGN(inode->i_extra_size, 8); + if (inode->i_extra) + len += ALIGN(inode->i_extra->size, 8); if (!(inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED)) { /* @@ -503,7 +504,8 @@ calculate_dentry_full_path(struct wim_dentry *dentry) d = dentry; do { p -= d->d_name_nbytes / sizeof(utf16lechar); - memcpy(p, d->d_name, d->d_name_nbytes); + if (d->d_name_nbytes) + memcpy(p, d->d_name, d->d_name_nbytes); *--p = cpu_to_le16(WIM_PATH_SEPARATOR); d = d->d_parent; /* assumes d == d->d_parent for root */ } while (!dentry_is_root(d)); @@ -1187,10 +1189,12 @@ read_extra_data(const u8 *p, const u8 *end, struct wim_inode *inode) p++; if (unlikely(p < end)) { - inode->i_extra = memdup(p, end - p); + inode->i_extra = MALLOC(sizeof(struct wim_inode_extra) + + end - p); if (!inode->i_extra) return WIMLIB_ERR_NOMEM; - inode->i_extra_size = end - p; + inode->i_extra->size = end - p; + memcpy(inode->i_extra->data, p, end - p); } return 0; } @@ -1227,6 +1231,12 @@ assign_stream_types_encrypted(struct wim_inode *inode) * There will be an unnamed data stream, a reparse point stream, or both an * unnamed data stream and a reparse point stream. In addition, there may be * named data streams. + * + * NOTE: if the file has a reparse point stream or at least one named data + * stream, then WIMGAPI puts *all* streams in the extra stream entries and + * leaves the default stream hash zeroed. wimlib now does the same. However, + * for input we still support the default hash field being used, since wimlib + * used to use it and MS software is somewhat accepting of it as well. */ static void assign_stream_types_unencrypted(struct wim_inode *inode) @@ -1241,7 +1251,10 @@ assign_stream_types_unencrypted(struct wim_inode *inode) if (stream_is_named(strm)) { /* Named data stream */ strm->stream_type = STREAM_TYPE_DATA; - } else if (!is_zero_hash(strm->_stream_hash)) { + } else if (i != 0 || !is_zero_hash(strm->_stream_hash)) { + /* Unnamed stream in the extra stream entries, OR the + * default stream in the dentry provided that it has a + * nonzero hash. */ if ((inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) && !found_reparse_point_stream) { found_reparse_point_stream = true; @@ -1250,17 +1263,21 @@ assign_stream_types_unencrypted(struct wim_inode *inode) found_unnamed_data_stream = true; strm->stream_type = STREAM_TYPE_DATA; } - } else { - /* If no stream name is specified and the hash is zero, - * then remember this stream for later so that we can - * assign it to the unnamed data stream if we don't find - * a better candidate. */ + } else if (!unnamed_stream_with_zero_hash) { unnamed_stream_with_zero_hash = strm; } } - if (!found_unnamed_data_stream && unnamed_stream_with_zero_hash != NULL) - unnamed_stream_with_zero_hash->stream_type = STREAM_TYPE_DATA; + if (unnamed_stream_with_zero_hash) { + int type = STREAM_TYPE_UNKNOWN; + if ((inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) && + !found_reparse_point_stream) { + type = STREAM_TYPE_REPARSE_POINT; + } else if (!found_unnamed_data_stream) { + type = STREAM_TYPE_DATA; + } + unnamed_stream_with_zero_hash->stream_type = type; + } } /* @@ -1381,7 +1398,7 @@ read_dentry(const u8 * restrict buf, size_t buf_len, u64 calculated_size; int ret; - BUILD_BUG_ON(sizeof(struct wim_dentry_on_disk) != WIM_DENTRY_DISK_SIZE); + STATIC_ASSERT(sizeof(struct wim_dentry_on_disk) == WIM_DENTRY_DISK_SIZE); /* Before reading the whole dentry, we need to read just the length. * This is because a dentry of length 8 (that is, just the length field) @@ -1515,7 +1532,6 @@ err_free_dentry: return ret; } -/* Is the dentry named "." or ".." ? */ static bool dentry_is_dot_or_dotdot(const struct wim_dentry *dentry) { @@ -1532,23 +1548,56 @@ dentry_is_dot_or_dotdot(const struct wim_dentry *dentry) return false; } +static bool +dentry_contains_embedded_null(const struct wim_dentry *dentry) +{ + for (unsigned i = 0; i < dentry->d_name_nbytes / 2; i++) + if (dentry->d_name[i] == cpu_to_le16('\0')) + return true; + return false; +} + +static bool +should_ignore_dentry(struct wim_dentry *dir, const struct wim_dentry *dentry) +{ + /* All dentries except the root must be named. */ + if (!dentry_has_long_name(dentry)) { + WARNING("Ignoring unnamed file in directory \"%"TS"\"", + dentry_full_path(dir)); + return true; + } + + /* Don't allow files named "." or "..". Such filenames could be used in + * path traversal attacks. */ + if (dentry_is_dot_or_dotdot(dentry)) { + WARNING("Ignoring file named \".\" or \"..\" in directory " + "\"%"TS"\"", dentry_full_path(dir)); + return true; + } + + /* Don't allow filenames containing embedded null characters. Although + * the null character is already considered an unsupported character for + * extraction by all targets, it is probably a good idea to just forbid + * such names entirely. */ + if (dentry_contains_embedded_null(dentry)) { + WARNING("Ignoring filename with embedded null character in " + "directory \"%"TS"\"", dentry_full_path(dir)); + return true; + } + + return false; +} + static int read_dentry_tree_recursive(const u8 * restrict buf, size_t buf_len, - struct wim_dentry * restrict dir) + struct wim_dentry * restrict dir, unsigned depth) { u64 cur_offset = dir->d_subdir_offset; - /* Check for cyclic directory structure, which would cause infinite - * recursion if not handled. */ - for (struct wim_dentry *d = dir->d_parent; - !dentry_is_root(d); d = d->d_parent) - { - if (unlikely(d->d_subdir_offset == cur_offset)) { - ERROR("Cyclic directory structure detected: children " - "of \"%"TS"\" coincide with children of \"%"TS"\"", - dentry_full_path(dir), dentry_full_path(d)); - return WIMLIB_ERR_INVALID_METADATA_RESOURCE; - } + /* Disallow extremely deep or cyclic directory structures */ + if (unlikely(depth >= 16384)) { + ERROR("Directory structure too deep!"); + return WIMLIB_ERR_INVALID_METADATA_RESOURCE; } for (;;) { @@ -1565,18 +1614,8 @@ read_dentry_tree_recursive(const u8 * restrict buf, size_t buf_len, if (child == NULL) return 0; - /* All dentries except the root should be named. */ - if (unlikely(!dentry_has_long_name(child))) { - WARNING("Ignoring unnamed dentry in " - "directory \"%"TS"\"", dentry_full_path(dir)); - free_dentry(child); - continue; - } - - /* Don't allow files named "." or "..". */ - if (unlikely(dentry_is_dot_or_dotdot(child))) { - WARNING("Ignoring file named \".\" or \"..\"; " - "potentially malicious archive!!!"); + /* Ignore dentries with bad names. */ + if (unlikely(should_ignore_dentry(dir, child))) { free_dentry(child); continue; } @@ -1601,7 +1640,8 @@ read_dentry_tree_recursive(const u8 * restrict buf, size_t buf_len, if (likely(dentry_is_directory(child))) { ret = read_dentry_tree_recursive(buf, buf_len, - child); + child, + depth + 1); if (ret) return ret; } else { @@ -1642,8 +1682,6 @@ read_dentry_tree(const u8 *buf, size_t buf_len, int ret; struct wim_dentry *root; - DEBUG("Reading dentry tree (root_offset=%"PRIu64")", root_offset); - ret = read_dentry(buf, buf_len, &root_offset, &root); if (ret) return ret; @@ -1664,7 +1702,7 @@ read_dentry_tree(const u8 *buf, size_t buf_len, } if (likely(root->d_subdir_offset != 0)) { - ret = read_dentry_tree_recursive(buf, buf_len, root); + ret = read_dentry_tree_recursive(buf, buf_len, root, 0); if (ret) goto err_free_dentry_tree; } @@ -1769,9 +1807,9 @@ write_dentry(const struct wim_dentry * restrict dentry, u8 * restrict p) while ((uintptr_t)p & 7) *p++ = 0; - if (inode->i_extra_size) { + if (inode->i_extra) { /* Extra tagged items --- not usually present. */ - p = mempcpy(p, inode->i_extra, inode->i_extra_size); + p = mempcpy(p, inode->i_extra->data, inode->i_extra->size); /* Align to 8-byte boundary */ while ((uintptr_t)p & 7) @@ -1890,10 +1928,6 @@ write_dir_dentries(struct wim_dentry *dir, void *_pp) u8 * write_dentry_tree(struct wim_dentry *root, u8 *p) { - DEBUG("Writing dentry tree."); - - wimlib_assert(root != NULL); - /* write root dentry and end-of-directory entry following it */ p = write_dentry(root, p); *(u64*)p = 0;