]> wimlib.net Git - wimlib/blob - src/metadata_resource.c
Prevent huge memory allocations from fuzzed header fields
[wimlib] / src / metadata_resource.c
1 /*
2  * metadata_resource.c
3  */
4
5 /*
6  * Copyright 2012-2023 Eric Biggers
7  *
8  * This file is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU Lesser General Public License as published by the Free
10  * Software Foundation; either version 3 of the License, or (at your option) any
11  * later version.
12  *
13  * This file is distributed in the hope that it will be useful, but WITHOUT
14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
16  * details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with this file; if not, see http://www.gnu.org/licenses/.
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #  include "config.h"
24 #endif
25
26 #include "wimlib/assert.h"
27 #include "wimlib/blob_table.h"
28 #include "wimlib/dentry.h"
29 #include "wimlib/error.h"
30 #include "wimlib/metadata.h"
31 #include "wimlib/resource.h"
32 #include "wimlib/security.h"
33 #include "wimlib/write.h"
34
35 /* Fix the security ID for every inode to be either -1 or in bounds.  */
36 static void
37 fix_security_ids(struct wim_image_metadata *imd, const u32 num_entries)
38 {
39         struct wim_inode *inode;
40         unsigned long invalid_count = 0;
41
42         image_for_each_inode(inode, imd) {
43                 if ((u32)inode->i_security_id >= num_entries) {
44                         if (inode->i_security_id >= 0)
45                                 invalid_count++;
46                         inode->i_security_id = -1;
47                 }
48         }
49         if (invalid_count)
50                 WARNING("%lu inodes had invalid security IDs", invalid_count);
51 }
52
53 /*
54  * Reads and parses a metadata resource for an image in the WIM file.
55  *
56  * @imd:
57  *      Pointer to the image metadata structure for the image whose metadata
58  *      resource we are reading.  Its `metadata_blob' member specifies the blob
59  *      table entry for the metadata resource.  The rest of the image metadata
60  *      entry will be filled in by this function.
61  *
62  * Return values:
63  *      WIMLIB_ERR_SUCCESS (0)
64  *      WIMLIB_ERR_INVALID_METADATA_RESOURCE
65  *      WIMLIB_ERR_NOMEM
66  *      WIMLIB_ERR_READ
67  *      WIMLIB_ERR_UNEXPECTED_END_OF_FILE
68  *      WIMLIB_ERR_DECOMPRESSION
69  */
70 int
71 read_metadata_resource(struct wim_image_metadata *imd)
72 {
73         const struct blob_descriptor *metadata_blob;
74         void *buf;
75         int ret;
76         u8 hash[SHA1_HASH_SIZE];
77         struct wim_security_data *sd;
78         struct wim_dentry *root;
79
80         metadata_blob = imd->metadata_blob;
81
82         /*
83          * Prevent huge memory allocations when processing fuzzed files.  The
84          * case of metadata resources is tough, since a metadata resource can
85          * legitimately decompress to many times the size of the WIM file
86          * itself, e.g. in the case of an image containing many empty files with
87          * similar long filenames.  Arbitrarily choose 512x as a generous limit.
88          */
89         if (metadata_blob->blob_location == BLOB_IN_WIM &&
90             metadata_blob->rdesc->wim->file_size > 0 &&
91             metadata_blob->size / 512 > metadata_blob->rdesc->wim->file_size)
92                 return WIMLIB_ERR_INVALID_METADATA_RESOURCE;
93
94         /* Read the metadata resource into memory.  (It may be compressed.)  */
95         ret = read_blob_into_alloc_buf(metadata_blob, &buf);
96         if (ret)
97                 return ret;
98
99         /* Checksum the metadata resource.  */
100         sha1(buf, metadata_blob->size, hash);
101         if (!hashes_equal(metadata_blob->hash, hash)) {
102                 ERROR("Metadata resource is corrupted "
103                       "(invalid SHA-1 message digest)!");
104                 ret = WIMLIB_ERR_INVALID_METADATA_RESOURCE;
105                 goto out_free_buf;
106         }
107
108         /* Parse the metadata resource.
109          *
110          * Notes: The metadata resource consists of the security data, followed
111          * by the directory entry for the root directory, followed by all the
112          * other directory entries in the filesystem.  The subdir offset field
113          * of each directory entry gives the start of its child entries from the
114          * beginning of the metadata resource.  An end-of-directory is signaled
115          * by a directory entry of length '0', really of length 8, because
116          * that's how long the 'length' field is.  */
117
118         ret = read_wim_security_data(buf, metadata_blob->size, &sd);
119         if (ret)
120                 goto out_free_buf;
121
122         ret = read_dentry_tree(buf, metadata_blob->size, sd->total_length, &root);
123         if (ret)
124                 goto out_free_security_data;
125
126         /* We have everything we need from the buffer now.  */
127         FREE(buf);
128         buf = NULL;
129
130         /* Calculate and validate inodes.  */
131
132         ret = dentry_tree_fix_inodes(root, &imd->inode_list);
133         if (ret)
134                 goto out_free_dentry_tree;
135
136         fix_security_ids(imd, sd->num_entries);
137
138         /* Success; fill in the image_metadata structure.  */
139         imd->root_dentry = root;
140         imd->security_data = sd;
141         INIT_LIST_HEAD(&imd->unhashed_blobs);
142         return 0;
143
144 out_free_dentry_tree:
145         free_dentry_tree(root, NULL);
146 out_free_security_data:
147         free_wim_security_data(sd);
148 out_free_buf:
149         FREE(buf);
150         return ret;
151 }
152
153 static void
154 recalculate_security_data_length(struct wim_security_data *sd)
155 {
156         u32 total_length = sizeof(u64) * sd->num_entries + 2 * sizeof(u32);
157         for (u32 i = 0; i < sd->num_entries; i++)
158                 total_length += sd->sizes[i];
159         sd->total_length = ALIGN(total_length, 8);
160 }
161
162 static int
163 prepare_metadata_resource(WIMStruct *wim, int image,
164                           u8 **buf_ret, size_t *len_ret)
165 {
166         u8 *buf;
167         u8 *p;
168         int ret;
169         u64 subdir_offset;
170         struct wim_dentry *root;
171         size_t len;
172         struct wim_security_data *sd;
173         struct wim_image_metadata *imd;
174
175         ret = select_wim_image(wim, image);
176         if (ret)
177                 return ret;
178
179         imd = wim->image_metadata[image - 1];
180
181         root = imd->root_dentry;
182         sd = imd->security_data;
183
184         if (!root) {
185                 /* Empty image; create a dummy root.  */
186                 ret = new_filler_directory(&root);
187                 if (ret)
188                         return ret;
189                 imd->root_dentry = root;
190         }
191
192         /* The offset of the first child of the root dentry is equal to the
193          * total length of the security data, plus the total length of the root
194          * dentry, plus 8 bytes for an end-of-directory entry following the root
195          * dentry (shouldn't really be needed, but just in case...)  */
196         recalculate_security_data_length(sd);
197         subdir_offset = sd->total_length + dentry_out_total_length(root) + 8;
198
199         /* Calculate the subdirectory offsets for the entire dentry tree.  */
200         calculate_subdir_offsets(root, &subdir_offset);
201
202         /* Total length of the metadata resource (uncompressed).  */
203         len = subdir_offset;
204
205         /* Allocate a buffer to contain the uncompressed metadata resource.  */
206         buf = NULL;
207         if (likely(len == subdir_offset))
208                 buf = MALLOC(len);
209         if (!buf) {
210                 ERROR("Failed to allocate %"PRIu64" bytes for "
211                       "metadata resource", subdir_offset);
212                 return WIMLIB_ERR_NOMEM;
213         }
214
215         /* Write the security data into the resource buffer.  */
216         p = write_wim_security_data(sd, buf);
217
218         /* Write the dentry tree into the resource buffer.  */
219         p = write_dentry_tree(root, p);
220
221         /* We MUST have exactly filled the buffer; otherwise we calculated its
222          * size incorrectly or wrote the data incorrectly.  */
223         wimlib_assert(p - buf == len);
224
225         *buf_ret = buf;
226         *len_ret = len;
227         return 0;
228 }
229
230 int
231 write_metadata_resource(WIMStruct *wim, int image, int write_resource_flags)
232 {
233         int ret;
234         u8 *buf;
235         size_t len;
236         struct wim_image_metadata *imd;
237
238         ret = prepare_metadata_resource(wim, image, &buf, &len);
239         if (ret)
240                 return ret;
241
242         imd = wim->image_metadata[image - 1];
243
244         /* Write the metadata resource to the output WIM using the proper
245          * compression type, in the process updating the blob descriptor for the
246          * metadata resource.  */
247         ret = write_wim_resource_from_buffer(buf,
248                                              len,
249                                              true,
250                                              &wim->out_fd,
251                                              wim->out_compression_type,
252                                              wim->out_chunk_size,
253                                              &imd->metadata_blob->out_reshdr,
254                                              imd->metadata_blob->hash,
255                                              write_resource_flags);
256
257         FREE(buf);
258         return ret;
259 }