xref: /linux/fs/ntfs3/attrlist.c (revision ca220141fa8ebae09765a242076b2b77338106b0)
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  */
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 
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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 
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