Fix tagged item parsing infinite loop when 32-bit and len=0xFFFFFFF8
[wimlib] / src / tagged_items.c
1 /*
2  * tagged_items.c
3  *
4  * Support for tagged metadata items that can be appended to WIM directory
5  * entries.
6  */
7
8 /*
9  * Copyright (C) 2014 Eric Biggers
10  *
11  * This file is free software; you can redistribute it and/or modify it under
12  * the terms of the GNU Lesser General Public License as published by the Free
13  * Software Foundation; either version 3 of the License, or (at your option) any
14  * later version.
15  *
16  * This file is distributed in the hope that it will be useful, but WITHOUT
17  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18  * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
19  * details.
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * along with this file; if not, see http://www.gnu.org/licenses/.
23  */
24
25 #ifdef HAVE_CONFIG_H
26 #  include "config.h"
27 #endif
28
29 #include "wimlib/endianness.h"
30 #include "wimlib/inode.h"
31 #include "wimlib/types.h"
32 #include "wimlib/unix_data.h"
33
34 /* Used by the Microsoft implementation.  */
35 #define TAG_OBJECT_ID           0x00000001
36
37 /* Random number that we'll use for tagging our UNIX data items.  */
38 #define TAG_WIMLIB_UNIX_DATA    0x337DD873
39
40 /* Header that begins each tagged metadata item in the metadata resource  */
41 struct tagged_item_header {
42
43         /* Unique identifier for this item.  */
44         le32 tag;
45
46         /* Size of the data of this tagged item, in bytes.  This excludes this
47          * header and should be a multiple of 8.  */
48         le32 length;
49
50         /* Variable length data  */
51         u8 data[];
52 };
53
54 struct object_id_disk {
55         u8 object_id[16];
56         u8 birth_volume_id[16];
57         u8 birth_object_id[16];
58         u8 domain_id[16];
59 };
60
61 struct wimlib_unix_data_disk {
62         le32 uid;
63         le32 gid;
64         le32 mode;
65         le32 rdev;
66 };
67
68 /* Retrieves the first tagged item with the specified tag and minimum length
69  * from the WIM inode.  Returns a pointer to the tagged data, which can be read
70  * and/or modified in place.  Or, if no matching tagged item is found, returns
71  * NULL.  */
72 static void *
73 inode_get_tagged_item(const struct wim_inode *inode,
74                       u32 desired_tag, u32 min_data_len)
75 {
76         size_t minlen_with_hdr = sizeof(struct tagged_item_header) + min_data_len;
77         size_t len_remaining = inode->i_extra_size;
78         u8 *p = inode->i_extra;
79
80         /* Iterate through the tagged items.  */
81         while (len_remaining >= minlen_with_hdr) {
82                 struct tagged_item_header *hdr;
83                 u32 tag;
84                 u32 len;
85
86                 hdr = (struct tagged_item_header *)p;
87                 tag = le32_to_cpu(hdr->tag);
88                 len = (le32_to_cpu(hdr->length) + 7) & ~7;
89
90                 /* Length overflow?  */
91                 if (unlikely(len > len_remaining - sizeof(struct tagged_item_header)))
92                         return NULL;
93
94                 /* Matches the item we wanted?  */
95                 if (tag == desired_tag && len >= min_data_len)
96                         return hdr->data;
97
98                 len_remaining -= sizeof(struct tagged_item_header) + len;
99                 p += sizeof(struct tagged_item_header) + len;
100         }
101         return NULL;
102 }
103
104 /* Adds a tagged item to a WIM inode and returns a pointer to its uninitialized
105  * data, which must be initialized in-place by the caller.  */
106 static void *
107 inode_add_tagged_item(struct wim_inode *inode, u32 tag, u32 len)
108 {
109         size_t itemsize;
110         size_t newsize;
111         u8 *buf;
112         struct tagged_item_header *hdr;
113
114         /* We prepend the item instead of appending it because it's easier.  */
115
116         itemsize = sizeof(struct tagged_item_header) + ((len + 7) & ~7);
117         newsize = itemsize + inode->i_extra_size;
118
119         buf = MALLOC(newsize);
120         if (!buf)
121                 return NULL;
122
123         if (inode->i_extra_size) {
124                 memcpy(buf + itemsize, inode->i_extra, inode->i_extra_size);
125                 FREE(inode->i_extra);
126         }
127         inode->i_extra = buf;
128         inode->i_extra_size = newsize;
129
130         hdr = (struct tagged_item_header *)buf;
131         hdr->tag = cpu_to_le32(tag);
132         hdr->length = cpu_to_le32(len);
133         return memset(hdr->data, 0, (len + 7) & ~7);
134 }
135
136 static inline struct wimlib_unix_data_disk *
137 inode_get_unix_data_disk(const struct wim_inode *inode)
138 {
139         return inode_get_tagged_item(inode, TAG_WIMLIB_UNIX_DATA,
140                                      sizeof(struct wimlib_unix_data_disk));
141 }
142
143 static inline struct wimlib_unix_data_disk *
144 inode_add_unix_data_disk(struct wim_inode *inode)
145 {
146         return inode_add_tagged_item(inode, TAG_WIMLIB_UNIX_DATA,
147                                      sizeof(struct wimlib_unix_data_disk));
148 }
149
150 /* Returns %true if the specified WIM inode has UNIX data; otherwise %false.
151  * This is a wimlib extension.  */
152 bool
153 inode_has_unix_data(const struct wim_inode *inode)
154 {
155         return inode_get_unix_data_disk(inode) != NULL;
156 }
157
158 /* Retrieves UNIX data from the specified WIM inode.
159  * This is a wimlib extension.
160  *
161  * Returns %true and fills @unix_data if the inode has UNIX data.
162  * Otherwise returns %false.  */
163 bool
164 inode_get_unix_data(const struct wim_inode *inode,
165                     struct wimlib_unix_data *unix_data)
166 {
167         const struct wimlib_unix_data_disk *p;
168
169         p = inode_get_unix_data_disk(inode);
170         if (!p)
171                 return false;
172
173         unix_data->uid = le32_to_cpu(p->uid);
174         unix_data->gid = le32_to_cpu(p->gid);
175         unix_data->mode = le32_to_cpu(p->mode);
176         unix_data->rdev = le32_to_cpu(p->rdev);
177         return true;
178 }
179
180 /* Sets UNIX data on the specified WIM inode.
181  * This is a wimlib extension.
182  *
183  * Callers must specify all members in @unix_data.  If the inode does not yet
184  * have UNIX data, it is given these values.  Otherwise, only the values that
185  * also have the corresponding flags in @which set are changed.
186  *
187  * Returns %true if successful, %false if failed (out of memory).  */
188 bool
189 inode_set_unix_data(struct wim_inode *inode, struct wimlib_unix_data *unix_data,
190                     int which)
191 {
192         struct wimlib_unix_data_disk *p;
193
194         p = inode_get_unix_data_disk(inode);
195         if (!p) {
196                 p = inode_add_unix_data_disk(inode);
197                 if (!p)
198                         return false;
199                 which = UNIX_DATA_ALL;
200         }
201         if (which & UNIX_DATA_UID)
202                 p->uid = cpu_to_le32(unix_data->uid);
203         if (which & UNIX_DATA_GID)
204                 p->gid = cpu_to_le32(unix_data->gid);
205         if (which & UNIX_DATA_MODE)
206                 p->mode = cpu_to_le32(unix_data->mode);
207         if (which & UNIX_DATA_RDEV)
208                 p->rdev = cpu_to_le32(unix_data->rdev);
209         return true;
210 }