1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 *
4 * Copyright (C) 2019-2021 Paragon Software GmbH, All rights reserved.
5 *
6 */
7
8 #include <linux/fs.h>
9
10 #include "debug.h"
11 #include "ntfs.h"
12 #include "ntfs_fs.h"
13
14 /*
15 * al_is_valid_le
16 *
17 * Return: True if @le is valid.
18 */
al_is_valid_le(const struct ntfs_inode * ni,struct ATTR_LIST_ENTRY * le)19 static inline bool al_is_valid_le(const struct ntfs_inode *ni,
20 struct ATTR_LIST_ENTRY *le)
21 {
22 if (!le || !ni->attr_list.le || !ni->attr_list.size)
23 return false;
24
25 return PtrOffset(ni->attr_list.le, le) + le16_to_cpu(le->size) <=
26 ni->attr_list.size;
27 }
28
al_destroy(struct ntfs_inode * ni)29 void al_destroy(struct ntfs_inode *ni)
30 {
31 run_close(&ni->attr_list.run);
32 kvfree(ni->attr_list.le);
33 ni->attr_list.le = NULL;
34 ni->attr_list.size = 0;
35 ni->attr_list.dirty = false;
36 }
37
38 /*
39 * ntfs_load_attr_list
40 *
41 * This method makes sure that the ATTRIB list, if present,
42 * has been properly set up.
43 */
ntfs_load_attr_list(struct ntfs_inode * ni,struct ATTRIB * attr)44 int ntfs_load_attr_list(struct ntfs_inode *ni, struct ATTRIB *attr)
45 {
46 int err;
47 size_t lsize;
48 void *le = NULL;
49
50 if (ni->attr_list.size)
51 return 0;
52
53 if (!attr->non_res) {
54 lsize = le32_to_cpu(attr->res.data_size);
55 if (!lsize) {
56 err = -EINVAL;
57 goto out;
58 }
59
60 /* attr is resident: lsize < record_size (1K or 4K) */
61 le = kvmalloc(al_aligned(lsize), GFP_KERNEL);
62 if (!le) {
63 err = -ENOMEM;
64 goto out;
65 }
66 memcpy(le, resident_data(attr), lsize);
67 } else if (attr->nres.svcn) {
68 err = -EINVAL;
69 goto out;
70 } else {
71 u16 run_off = le16_to_cpu(attr->nres.run_off);
72
73 lsize = le64_to_cpu(attr->nres.data_size);
74 if (!lsize) {
75 err = -EINVAL;
76 goto out;
77 }
78
79 run_init(&ni->attr_list.run);
80
81 if (run_off > le32_to_cpu(attr->size)) {
82 err = -EINVAL;
83 goto out;
84 }
85
86 err = run_unpack_ex(&ni->attr_list.run, ni->mi.sbi, ni->mi.rno,
87 0, le64_to_cpu(attr->nres.evcn), 0,
88 Add2Ptr(attr, run_off),
89 le32_to_cpu(attr->size) - run_off);
90 if (err < 0)
91 goto out;
92
93 /* attr is nonresident.
94 * The worst case:
95 * 1T (2^40) extremely fragmented file.
96 * cluster = 4K (2^12) => 2^28 fragments
97 * 2^9 fragments per one record => 2^19 records
98 * 2^5 bytes of ATTR_LIST_ENTRY per one record => 2^24 bytes.
99 *
100 * the result is 16M bytes per attribute list.
101 * Use kvmalloc to allocate in range [several Kbytes - dozen Mbytes]
102 */
103 le = kvmalloc(al_aligned(lsize), GFP_KERNEL);
104 if (!le) {
105 err = -ENOMEM;
106 goto out;
107 }
108
109 err = ntfs_read_run_nb(ni->mi.sbi, &ni->attr_list.run, 0, le,
110 lsize, NULL);
111 if (err)
112 goto out;
113 }
114
115 ni->attr_list.size = lsize;
116 ni->attr_list.le = le;
117
118 return 0;
119
120 out:
121 ni->attr_list.le = le;
122 al_destroy(ni);
123
124 return err;
125 }
126
127 /*
128 * al_enumerate
129 *
130 * Return:
131 * * The next list le.
132 * * If @le is NULL then return the first le.
133 */
al_enumerate(struct ntfs_inode * ni,struct ATTR_LIST_ENTRY * le)134 struct ATTR_LIST_ENTRY *al_enumerate(struct ntfs_inode *ni,
135 struct ATTR_LIST_ENTRY *le)
136 {
137 size_t off;
138 u16 sz;
139 const unsigned le_min_size = le_size(0);
140
141 if (!le) {
142 le = ni->attr_list.le;
143 } else {
144 sz = le16_to_cpu(le->size);
145 if (sz < le_min_size) {
146 /* Impossible 'cause we should not return such le. */
147 return NULL;
148 }
149 le = Add2Ptr(le, sz);
150 }
151
152 /* Check boundary. */
153 off = PtrOffset(ni->attr_list.le, le);
154 if (off + le_min_size > ni->attr_list.size) {
155 /* The regular end of list. */
156 return NULL;
157 }
158
159 sz = le16_to_cpu(le->size);
160
161 /* Check le for errors. */
162 if (sz < le_min_size || off + sz > ni->attr_list.size ||
163 sz < le->name_off + le->name_len * sizeof(short)) {
164 return NULL;
165 }
166
167 return le;
168 }
169
170 /*
171 * al_find_le
172 *
173 * Find the first le in the list which matches type, name and VCN.
174 *
175 * Return: NULL if not found.
176 */
al_find_le(struct ntfs_inode * ni,struct ATTR_LIST_ENTRY * le,const struct ATTRIB * attr)177 struct ATTR_LIST_ENTRY *al_find_le(struct ntfs_inode *ni,
178 struct ATTR_LIST_ENTRY *le,
179 const struct ATTRIB *attr)
180 {
181 CLST svcn = attr_svcn(attr);
182
183 return al_find_ex(ni, le, attr->type, attr_name(attr), attr->name_len,
184 &svcn);
185 }
186
187 /*
188 * al_find_ex
189 *
190 * Find the first le in the list which matches type, name and VCN.
191 *
192 * Return: NULL if not found.
193 */
al_find_ex(struct ntfs_inode * ni,struct ATTR_LIST_ENTRY * le,enum ATTR_TYPE type,const __le16 * name,u8 name_len,const CLST * vcn)194 struct ATTR_LIST_ENTRY *al_find_ex(struct ntfs_inode *ni,
195 struct ATTR_LIST_ENTRY *le,
196 enum ATTR_TYPE type, const __le16 *name,
197 u8 name_len, const CLST *vcn)
198 {
199 struct ATTR_LIST_ENTRY *ret = NULL;
200 u32 type_in = le32_to_cpu(type);
201
202 while ((le = al_enumerate(ni, le))) {
203 u64 le_vcn;
204 int diff = le32_to_cpu(le->type) - type_in;
205
206 /* List entries are sorted by type, name and VCN. */
207 if (diff < 0)
208 continue;
209
210 if (diff > 0)
211 return ret;
212
213 if (le->name_len != name_len)
214 continue;
215
216 le_vcn = le64_to_cpu(le->vcn);
217 if (!le_vcn) {
218 /*
219 * Compare entry names only for entry with vcn == 0.
220 */
221 diff = ntfs_cmp_names(le_name(le), name_len, name,
222 name_len, ni->mi.sbi->upcase,
223 true);
224 if (diff < 0)
225 continue;
226
227 if (diff > 0)
228 return ret;
229 }
230
231 if (!vcn)
232 return le;
233
234 if (*vcn == le_vcn)
235 return le;
236
237 if (*vcn < le_vcn)
238 return ret;
239
240 ret = le;
241 }
242
243 return ret;
244 }
245
246 /*
247 * al_find_le_to_insert
248 *
249 * Find the first list entry which matches type, name and VCN.
250 */
al_find_le_to_insert(struct ntfs_inode * ni,enum ATTR_TYPE type,const __le16 * name,u8 name_len,CLST vcn)251 static struct ATTR_LIST_ENTRY *al_find_le_to_insert(struct ntfs_inode *ni,
252 enum ATTR_TYPE type,
253 const __le16 *name,
254 u8 name_len, CLST vcn)
255 {
256 struct ATTR_LIST_ENTRY *le = NULL, *prev;
257 u32 type_in = le32_to_cpu(type);
258
259 /* List entries are sorted by type, name and VCN. */
260 while ((le = al_enumerate(ni, prev = le))) {
261 int diff = le32_to_cpu(le->type) - type_in;
262
263 if (diff < 0)
264 continue;
265
266 if (diff > 0)
267 return le;
268
269 if (!le->vcn) {
270 /*
271 * Compare entry names only for entry with vcn == 0.
272 */
273 diff = ntfs_cmp_names(le_name(le), le->name_len, name,
274 name_len, ni->mi.sbi->upcase,
275 true);
276 if (diff < 0)
277 continue;
278
279 if (diff > 0)
280 return le;
281 }
282
283 if (le64_to_cpu(le->vcn) >= vcn)
284 return le;
285 }
286
287 return prev ? Add2Ptr(prev, le16_to_cpu(prev->size)) : ni->attr_list.le;
288 }
289
290 /*
291 * al_add_le
292 *
293 * Add an "attribute list entry" to the list.
294 */
al_add_le(struct ntfs_inode * ni,enum ATTR_TYPE type,const __le16 * name,u8 name_len,CLST svcn,__le16 id,const struct MFT_REF * ref,struct ATTR_LIST_ENTRY ** new_le)295 int al_add_le(struct ntfs_inode *ni, enum ATTR_TYPE type, const __le16 *name,
296 u8 name_len, CLST svcn, __le16 id, const struct MFT_REF *ref,
297 struct ATTR_LIST_ENTRY **new_le)
298 {
299 int err;
300 struct ATTRIB *attr;
301 struct ATTR_LIST_ENTRY *le;
302 size_t off;
303 u16 sz;
304 size_t asize, new_asize, old_size;
305 u64 new_size;
306 typeof(ni->attr_list) *al = &ni->attr_list;
307
308 /*
309 * Compute the size of the new 'le'
310 */
311 sz = le_size(name_len);
312 old_size = al->size;
313 new_size = old_size + sz;
314 asize = al_aligned(old_size);
315 new_asize = al_aligned(new_size);
316
317 /* Scan forward to the point at which the new 'le' should be inserted. */
318 le = al_find_le_to_insert(ni, type, name, name_len, svcn);
319 off = PtrOffset(al->le, le);
320
321 if (new_size > asize) {
322 void *ptr = kmalloc(new_asize, GFP_NOFS);
323
324 if (!ptr)
325 return -ENOMEM;
326
327 memcpy(ptr, al->le, off);
328 memcpy(Add2Ptr(ptr, off + sz), le, old_size - off);
329 le = Add2Ptr(ptr, off);
330 kvfree(al->le);
331 al->le = ptr;
332 } else {
333 memmove(Add2Ptr(le, sz), le, old_size - off);
334 }
335 *new_le = le;
336
337 al->size = new_size;
338
339 le->type = type;
340 le->size = cpu_to_le16(sz);
341 le->name_len = name_len;
342 le->name_off = offsetof(struct ATTR_LIST_ENTRY, name);
343 le->vcn = cpu_to_le64(svcn);
344 le->ref = *ref;
345 le->id = id;
346 memcpy(le->name, name, sizeof(short) * name_len);
347
348 err = attr_set_size_ex(ni, ATTR_LIST, NULL, 0, &al->run, new_size,
349 &new_size, true, &attr, false);
350 if (err) {
351 /* Undo memmove above. */
352 memmove(le, Add2Ptr(le, sz), old_size - off);
353 al->size = old_size;
354 return err;
355 }
356
357 al->dirty = true;
358
359 if (attr && attr->non_res) {
360 err = ntfs_sb_write_run(ni->mi.sbi, &al->run, 0, al->le,
361 al->size, 0);
362 if (err)
363 return err;
364 al->dirty = false;
365 }
366
367 return 0;
368 }
369
370 /*
371 * al_remove_le - Remove @le from attribute list.
372 */
al_remove_le(struct ntfs_inode * ni,struct ATTR_LIST_ENTRY * le)373 bool al_remove_le(struct ntfs_inode *ni, struct ATTR_LIST_ENTRY *le)
374 {
375 u16 size;
376 size_t off;
377 typeof(ni->attr_list) *al = &ni->attr_list;
378
379 if (!al_is_valid_le(ni, le))
380 return false;
381
382 /* Save on stack the size of 'le' */
383 size = le16_to_cpu(le->size);
384 off = PtrOffset(al->le, le);
385
386 memmove(le, Add2Ptr(le, size), al->size - (off + size));
387
388 al->size -= size;
389 al->dirty = true;
390
391 return true;
392 }
393
al_update(struct ntfs_inode * ni,int sync)394 int al_update(struct ntfs_inode *ni, int sync)
395 {
396 int err;
397 struct ATTRIB *attr;
398 typeof(ni->attr_list) *al = &ni->attr_list;
399
400 if (!al->dirty || !al->size)
401 return 0;
402
403 /*
404 * Attribute list increased on demand in al_add_le.
405 * Attribute list decreased here.
406 */
407 err = attr_set_size_ex(ni, ATTR_LIST, NULL, 0, &al->run, al->size, NULL,
408 false, &attr, false);
409 if (err)
410 goto out;
411
412 if (!attr->non_res) {
413 memcpy(resident_data(attr), al->le, al->size);
414 } else {
415 err = ntfs_sb_write_run(ni->mi.sbi, &al->run, 0, al->le,
416 al->size, sync);
417 if (err)
418 goto out;
419
420 attr->nres.valid_size = attr->nres.data_size;
421 }
422
423 ni->mi.dirty = true;
424 al->dirty = false;
425
426 out:
427 return err;
428 }
429