xref: /linux/fs/ntfs/reparse.c (revision cdd4dc3aebeab43a72ce0bc2b5bab6f0a80b97a5)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Processing of reparse points
4  *
5  * Part of this file is based on code from the NTFS-3G.
6  *
7  * Copyright (c) 2008-2021 Jean-Pierre Andre
8  * Copyright (c) 2025 LG Electronics Co., Ltd.
9  */
10 
11 #include "ntfs.h"
12 #include "layout.h"
13 #include "attrib.h"
14 #include "inode.h"
15 #include "dir.h"
16 #include "volume.h"
17 #include "mft.h"
18 #include "index.h"
19 #include "lcnalloc.h"
20 #include "reparse.h"
21 
22 struct wsl_link_reparse_data {
23 	__le32	type;
24 	char	link[];
25 };
26 
27 /* Index entry in $Extend/$Reparse */
28 struct reparse_index {
29 	struct index_entry_header header;
30 	struct reparse_index_key key;
31 	__le32 filling;
32 };
33 
34 __le16 reparse_index_name[] = {cpu_to_le16('$'), cpu_to_le16('R'), 0};
35 
36 
37 /*
38  * Check if the reparse point attribute buffer is valid.
39  * Returns true if valid, false otherwise.
40  */
41 static bool ntfs_is_valid_reparse_buffer(struct ntfs_inode *ni,
42 		const struct reparse_point *reparse_attr, size_t size)
43 {
44 	size_t expected;
45 
46 	if (!ni || !reparse_attr)
47 		return false;
48 
49 	/* Minimum size must cover reparse_point header */
50 	if (size < sizeof(struct reparse_point))
51 		return false;
52 
53 	/* Reserved zero tag is invalid */
54 	if (reparse_attr->reparse_tag == IO_REPARSE_TAG_RESERVED_ZERO)
55 		return false;
56 
57 	/* Calculate expected total size */
58 	expected = sizeof(struct reparse_point) +
59 		le16_to_cpu(reparse_attr->reparse_data_length);
60 
61 	/* Add GUID size for non-Microsoft tags */
62 	if (!(reparse_attr->reparse_tag & IO_REPARSE_TAG_IS_MICROSOFT))
63 		expected += sizeof(struct guid);
64 
65 	/* Buffer must exactly match the expected size */
66 	return expected == size;
67 }
68 
69 /*
70  * Do some sanity checks on reparse data
71  *
72  * Microsoft reparse points have an 8-byte header whereas
73  * non-Microsoft reparse points have a 24-byte header.  In each case,
74  * 'reparse_data_length' must equal the number of non-header bytes.
75  *
76  * If the reparse data looks like a junction point or symbolic
77  * link, more checks can be done.
78  */
79 static bool valid_reparse_data(struct ntfs_inode *ni,
80 		const struct reparse_point *reparse_attr, size_t size)
81 {
82 	const struct wsl_link_reparse_data *wsl_reparse_data =
83 		(const struct wsl_link_reparse_data *)reparse_attr->reparse_data;
84 	unsigned int data_len = le16_to_cpu(reparse_attr->reparse_data_length);
85 
86 	if (ntfs_is_valid_reparse_buffer(ni, reparse_attr, size) == false)
87 		return false;
88 
89 	switch (reparse_attr->reparse_tag) {
90 	case IO_REPARSE_TAG_LX_SYMLINK:
91 		if (data_len <= sizeof(wsl_reparse_data->type) ||
92 		    wsl_reparse_data->type != cpu_to_le32(2))
93 			return false;
94 		break;
95 	case IO_REPARSE_TAG_AF_UNIX:
96 	case IO_REPARSE_TAG_LX_FIFO:
97 	case IO_REPARSE_TAG_LX_CHR:
98 	case IO_REPARSE_TAG_LX_BLK:
99 		if (data_len || !(ni->flags & FILE_ATTRIBUTE_RECALL_ON_OPEN))
100 			return false;
101 	}
102 
103 	return true;
104 }
105 
106 static unsigned int ntfs_reparse_tag_mode(struct reparse_point *reparse_attr)
107 {
108 	unsigned int mode = 0;
109 
110 	switch (reparse_attr->reparse_tag) {
111 	case IO_REPARSE_TAG_SYMLINK:
112 	case IO_REPARSE_TAG_LX_SYMLINK:
113 		mode = S_IFLNK;
114 		break;
115 	case IO_REPARSE_TAG_AF_UNIX:
116 		mode = S_IFSOCK;
117 		break;
118 	case IO_REPARSE_TAG_LX_FIFO:
119 		mode = S_IFIFO;
120 		break;
121 	case IO_REPARSE_TAG_LX_CHR:
122 		mode = S_IFCHR;
123 		break;
124 	case IO_REPARSE_TAG_LX_BLK:
125 		mode = S_IFBLK;
126 	}
127 
128 	return mode;
129 }
130 
131 /*
132  * Get the target for symbolic link
133  */
134 unsigned int ntfs_make_symlink(struct ntfs_inode *ni)
135 {
136 	s64 attr_size = 0;
137 	unsigned int lth;
138 	struct reparse_point *reparse_attr;
139 	struct wsl_link_reparse_data *wsl_link_data;
140 	unsigned int mode = 0;
141 
142 	reparse_attr = ntfs_attr_readall(ni, AT_REPARSE_POINT, NULL, 0,
143 					 &attr_size);
144 	if (reparse_attr && attr_size &&
145 	    valid_reparse_data(ni, reparse_attr, attr_size)) {
146 		switch (reparse_attr->reparse_tag) {
147 		case IO_REPARSE_TAG_LX_SYMLINK:
148 			wsl_link_data =
149 				(struct wsl_link_reparse_data *)reparse_attr->reparse_data;
150 			if (wsl_link_data->type == cpu_to_le32(2)) {
151 				lth = le16_to_cpu(reparse_attr->reparse_data_length) -
152 						  sizeof(wsl_link_data->type);
153 				ni->target = kvzalloc(lth + 1, GFP_NOFS);
154 				if (ni->target) {
155 					memcpy(ni->target, wsl_link_data->link, lth);
156 					ni->target[lth] = 0;
157 					mode = ntfs_reparse_tag_mode(reparse_attr);
158 				}
159 			}
160 			break;
161 		default:
162 			mode = ntfs_reparse_tag_mode(reparse_attr);
163 		}
164 	} else
165 		ni->flags &= ~FILE_ATTR_REPARSE_POINT;
166 
167 	if (reparse_attr)
168 		kvfree(reparse_attr);
169 
170 	return mode;
171 }
172 
173 unsigned int ntfs_reparse_tag_dt_types(struct ntfs_volume *vol, unsigned long mref)
174 {
175 	s64 attr_size = 0;
176 	struct reparse_point *reparse_attr;
177 	unsigned int dt_type = DT_UNKNOWN;
178 	struct inode *vi;
179 
180 	vi = ntfs_iget(vol->sb, mref);
181 	if (IS_ERR(vi))
182 		return PTR_ERR(vi);
183 
184 	reparse_attr = (struct reparse_point *)ntfs_attr_readall(NTFS_I(vi),
185 			AT_REPARSE_POINT, NULL, 0, &attr_size);
186 
187 	if (reparse_attr && attr_size) {
188 		switch (reparse_attr->reparse_tag) {
189 		case IO_REPARSE_TAG_SYMLINK:
190 		case IO_REPARSE_TAG_LX_SYMLINK:
191 			dt_type = DT_LNK;
192 			break;
193 		case IO_REPARSE_TAG_AF_UNIX:
194 			dt_type = DT_SOCK;
195 			break;
196 		case IO_REPARSE_TAG_LX_FIFO:
197 			dt_type = DT_FIFO;
198 			break;
199 		case IO_REPARSE_TAG_LX_CHR:
200 			dt_type = DT_CHR;
201 			break;
202 		case IO_REPARSE_TAG_LX_BLK:
203 			dt_type = DT_BLK;
204 		}
205 	}
206 
207 	if (reparse_attr)
208 		kvfree(reparse_attr);
209 
210 	iput(vi);
211 	return dt_type;
212 }
213 
214 /*
215  * Set the index for new reparse data
216  */
217 static int set_reparse_index(struct ntfs_inode *ni, struct ntfs_index_context *xr,
218 		__le32 reparse_tag)
219 {
220 	struct reparse_index indx;
221 	u64 file_id_cpu;
222 	__le64 file_id;
223 
224 	file_id_cpu = MK_MREF(ni->mft_no, ni->seq_no);
225 	file_id = cpu_to_le64(file_id_cpu);
226 	indx.header.data.vi.data_offset =
227 		cpu_to_le16(sizeof(struct index_entry_header) + sizeof(struct reparse_index_key));
228 	indx.header.data.vi.data_length = 0;
229 	indx.header.data.vi.reservedV = 0;
230 	indx.header.length = cpu_to_le16(sizeof(struct reparse_index));
231 	indx.header.key_length = cpu_to_le16(sizeof(struct reparse_index_key));
232 	indx.header.flags = 0;
233 	indx.header.reserved = 0;
234 	indx.key.reparse_tag = reparse_tag;
235 	/* danger on processors which require proper alignment! */
236 	memcpy(&indx.key.file_id, &file_id, 8);
237 	indx.filling = 0;
238 	ntfs_index_ctx_reinit(xr);
239 
240 	return ntfs_ie_add(xr, (struct index_entry *)&indx);
241 }
242 
243 /*
244  * Remove a reparse data index entry if attribute present
245  */
246 static int remove_reparse_index(struct inode *rp, struct ntfs_index_context *xr,
247 				__le32 *preparse_tag)
248 {
249 	struct reparse_index_key key;
250 	u64 file_id_cpu;
251 	__le64 file_id;
252 	s64 size;
253 	struct ntfs_inode *ni = NTFS_I(rp);
254 	int err = 0, ret = ni->data_size;
255 
256 	if (ni->data_size == 0)
257 		return 0;
258 
259 	/* read the existing reparse_tag */
260 	size = ntfs_inode_attr_pread(rp, 0, 4, (char *)preparse_tag);
261 	if (size != 4)
262 		return -ENODATA;
263 
264 	file_id_cpu = MK_MREF(ni->mft_no, ni->seq_no);
265 	file_id = cpu_to_le64(file_id_cpu);
266 	key.reparse_tag = *preparse_tag;
267 	/* danger on processors which require proper alignment! */
268 	memcpy(&key.file_id, &file_id, 8);
269 	if (!ntfs_index_lookup(&key, sizeof(struct reparse_index_key), xr)) {
270 		err = ntfs_index_rm(xr);
271 		if (err)
272 			ret = err;
273 	}
274 	return ret;
275 }
276 
277 /*
278  * Open the $Extend/$Reparse file and its index
279  */
280 static struct ntfs_index_context *open_reparse_index(struct ntfs_volume *vol)
281 {
282 	struct ntfs_index_context *xr = NULL;
283 	u64 mref;
284 	__le16 *uname;
285 	struct ntfs_name *name = NULL;
286 	int uname_len;
287 	struct inode *vi, *dir_vi;
288 
289 	/* do not use path_name_to inode - could reopen root */
290 	dir_vi = ntfs_iget(vol->sb, FILE_Extend);
291 	if (IS_ERR(dir_vi))
292 		return NULL;
293 
294 	uname_len = ntfs_nlstoucs(vol, "$Reparse", 8, &uname,
295 				  NTFS_MAX_NAME_LEN);
296 	if (uname_len < 0) {
297 		iput(dir_vi);
298 		return NULL;
299 	}
300 
301 	mutex_lock_nested(&NTFS_I(dir_vi)->mrec_lock, NTFS_EXTEND_MUTEX_PARENT);
302 	mref = ntfs_lookup_inode_by_name(NTFS_I(dir_vi), uname, uname_len,
303 					 &name);
304 	mutex_unlock(&NTFS_I(dir_vi)->mrec_lock);
305 	kfree(name);
306 	kmem_cache_free(ntfs_name_cache, uname);
307 	if (IS_ERR_MREF(mref))
308 		goto put_dir_vi;
309 
310 	vi = ntfs_iget(vol->sb, MREF(mref));
311 	if (IS_ERR(vi))
312 		goto put_dir_vi;
313 
314 	xr = ntfs_index_ctx_get(NTFS_I(vi), reparse_index_name, 2);
315 	if (!xr)
316 		iput(vi);
317 put_dir_vi:
318 	iput(dir_vi);
319 	return xr;
320 }
321 
322 
323 /*
324  * Update the reparse data and index
325  *
326  * The reparse data attribute should have been created, and
327  * an existing index is expected if there is an existing value.
328  *
329  */
330 static int update_reparse_data(struct ntfs_inode *ni, struct ntfs_index_context *xr,
331 		char *value, size_t size)
332 {
333 	struct inode *rp_inode;
334 	int err = 0;
335 	s64 written;
336 	int oldsize;
337 	__le32 reparse_tag;
338 	struct ntfs_inode *rp_ni;
339 
340 	rp_inode = ntfs_attr_iget(VFS_I(ni), AT_REPARSE_POINT, AT_UNNAMED, 0);
341 	if (IS_ERR(rp_inode))
342 		return -EINVAL;
343 	rp_ni = NTFS_I(rp_inode);
344 
345 	/* remove the existing reparse data */
346 	oldsize = remove_reparse_index(rp_inode, xr, &reparse_tag);
347 	if (oldsize < 0) {
348 		err = oldsize;
349 		goto put_rp_inode;
350 	}
351 
352 	/* overwrite value if any */
353 	written = ntfs_inode_attr_pwrite(rp_inode, 0, size, value, false);
354 	if (written != size) {
355 		ntfs_error(ni->vol->sb, "Failed to update reparse data\n");
356 		err = -EIO;
357 		goto put_rp_inode;
358 	}
359 
360 	if (set_reparse_index(ni, xr, ((const struct reparse_point *)value)->reparse_tag) &&
361 	    oldsize > 0) {
362 		/*
363 		 * If cannot index, try to remove the reparse
364 		 * data and log the error. There will be an
365 		 * inconsistency if removal fails.
366 		 */
367 		ntfs_attr_rm(rp_ni);
368 		ntfs_error(ni->vol->sb,
369 			   "Failed to index reparse data. Possible corruption.\n");
370 	}
371 
372 	mark_mft_record_dirty(ni);
373 put_rp_inode:
374 	iput(rp_inode);
375 
376 	return err;
377 }
378 
379 /*
380  * Delete a reparse index entry
381  */
382 int ntfs_delete_reparse_index(struct ntfs_inode *ni)
383 {
384 	struct inode *vi;
385 	struct ntfs_index_context *xr;
386 	struct ntfs_inode *xrni;
387 	__le32 reparse_tag;
388 	int err = 0;
389 
390 	if (!(ni->flags & FILE_ATTR_REPARSE_POINT))
391 		return 0;
392 
393 	vi = ntfs_attr_iget(VFS_I(ni), AT_REPARSE_POINT, AT_UNNAMED, 0);
394 	if (IS_ERR(vi))
395 		return PTR_ERR(vi);
396 
397 	/*
398 	 * read the existing reparse data (the tag is enough)
399 	 * and un-index it
400 	 */
401 	xr = open_reparse_index(ni->vol);
402 	if (xr) {
403 		xrni = xr->idx_ni;
404 		mutex_lock_nested(&xrni->mrec_lock, NTFS_EXTEND_MUTEX_PARENT);
405 		err = remove_reparse_index(vi, xr, &reparse_tag);
406 		if (err < 0) {
407 			ntfs_index_ctx_put(xr);
408 			mutex_unlock(&xrni->mrec_lock);
409 			iput(VFS_I(xrni));
410 			goto out;
411 		}
412 		mark_mft_record_dirty(xrni);
413 		ntfs_index_ctx_put(xr);
414 		mutex_unlock(&xrni->mrec_lock);
415 		iput(VFS_I(xrni));
416 	}
417 
418 	ni->flags &= ~FILE_ATTR_REPARSE_POINT;
419 	NInoSetFileNameDirty(ni);
420 	mark_mft_record_dirty(ni);
421 
422 out:
423 	iput(vi);
424 	return err;
425 }
426 
427 /*
428  * Set the reparse data from an extended attribute
429  */
430 static int ntfs_set_ntfs_reparse_data(struct ntfs_inode *ni, char *value, size_t size)
431 {
432 	int err = 0;
433 	struct ntfs_inode *xrni;
434 	struct ntfs_index_context *xr;
435 
436 	if (!ni)
437 		return -EINVAL;
438 
439 	/*
440 	 * reparse data compatibily with EA is not checked
441 	 * any more, it is required by Windows 10, but may
442 	 * lead to problems with earlier versions.
443 	 */
444 	if (valid_reparse_data(ni, (const struct reparse_point *)value, size) == false)
445 		return -EINVAL;
446 
447 	xr = open_reparse_index(ni->vol);
448 	if (!xr)
449 		return -EINVAL;
450 	xrni = xr->idx_ni;
451 
452 	if (!ntfs_attr_exist(ni, AT_REPARSE_POINT, AT_UNNAMED, 0)) {
453 		struct reparse_point rp = {0, };
454 
455 		/*
456 		 * no reparse data attribute : add one,
457 		 * apparently, this does not feed the new value in
458 		 * Note : NTFS version must be >= 3
459 		 */
460 		if (ni->vol->major_ver < 3) {
461 			err = -EOPNOTSUPP;
462 			ntfs_index_ctx_put(xr);
463 			goto out;
464 		}
465 
466 		err = ntfs_attr_add(ni, AT_REPARSE_POINT, AT_UNNAMED, 0, (u8 *)&rp, sizeof(rp));
467 		if (err) {
468 			ntfs_index_ctx_put(xr);
469 			goto out;
470 		}
471 		ni->flags |= FILE_ATTR_REPARSE_POINT;
472 		NInoSetFileNameDirty(ni);
473 		mark_mft_record_dirty(ni);
474 	}
475 
476 	/* update value and index */
477 	mutex_lock_nested(&xrni->mrec_lock, NTFS_EXTEND_MUTEX_PARENT);
478 	err = update_reparse_data(ni, xr, value, size);
479 	if (err) {
480 		ni->flags &= ~FILE_ATTR_REPARSE_POINT;
481 		NInoSetFileNameDirty(ni);
482 		mark_mft_record_dirty(ni);
483 	}
484 	ntfs_index_ctx_put(xr);
485 	mutex_unlock(&xrni->mrec_lock);
486 
487 out:
488 	if (!err)
489 		mark_mft_record_dirty(xrni);
490 	iput(VFS_I(xrni));
491 
492 	return err;
493 }
494 
495 /*
496  * Set reparse data for a WSL type symlink
497  */
498 int ntfs_reparse_set_wsl_symlink(struct ntfs_inode *ni,
499 		const __le16 *target, int target_len)
500 {
501 	int err = 0;
502 	int len;
503 	int reparse_len;
504 	unsigned char *utarget = NULL;
505 	struct reparse_point *reparse;
506 	struct wsl_link_reparse_data *data;
507 
508 	utarget = (char *)NULL;
509 	len = ntfs_ucstonls(ni->vol, target, target_len, &utarget, 0);
510 	if (len <= 0)
511 		return -EINVAL;
512 
513 	reparse_len = sizeof(struct reparse_point) + sizeof(data->type) + len;
514 	reparse = kvzalloc(reparse_len, GFP_NOFS);
515 	if (!reparse) {
516 		err = -ENOMEM;
517 		kvfree(utarget);
518 	} else {
519 		data = (struct wsl_link_reparse_data *)reparse->reparse_data;
520 		reparse->reparse_tag = IO_REPARSE_TAG_LX_SYMLINK;
521 		reparse->reparse_data_length =
522 			cpu_to_le16(sizeof(data->type) + len);
523 		reparse->reserved = 0;
524 		data->type = cpu_to_le32(2);
525 		memcpy(data->link, utarget, len);
526 		err = ntfs_set_ntfs_reparse_data(ni,
527 				(char *)reparse, reparse_len);
528 		kvfree(reparse);
529 		if (!err)
530 			ni->target = utarget;
531 	}
532 	return err;
533 }
534 
535 /*
536  * Set reparse data for a WSL special file other than a symlink
537  * (socket, fifo, character or block device)
538  */
539 int ntfs_reparse_set_wsl_not_symlink(struct ntfs_inode *ni, mode_t mode)
540 {
541 	int err;
542 	int len;
543 	int reparse_len;
544 	__le32 reparse_tag;
545 	struct reparse_point *reparse;
546 
547 	len = 0;
548 	if (S_ISSOCK(mode))
549 		reparse_tag = IO_REPARSE_TAG_AF_UNIX;
550 	else if (S_ISFIFO(mode))
551 		reparse_tag = IO_REPARSE_TAG_LX_FIFO;
552 	else if (S_ISCHR(mode))
553 		reparse_tag = IO_REPARSE_TAG_LX_CHR;
554 	else if (S_ISBLK(mode))
555 		reparse_tag = IO_REPARSE_TAG_LX_BLK;
556 	else
557 		return -EOPNOTSUPP;
558 
559 	reparse_len = sizeof(struct reparse_point) + len;
560 	reparse = kvzalloc(reparse_len, GFP_NOFS);
561 	if (!reparse)
562 		err = -ENOMEM;
563 	else {
564 		reparse->reparse_tag = reparse_tag;
565 		reparse->reparse_data_length = cpu_to_le16(len);
566 		reparse->reserved = cpu_to_le16(0);
567 		err = ntfs_set_ntfs_reparse_data(ni, (char *)reparse,
568 						 reparse_len);
569 		kvfree(reparse);
570 	}
571 
572 	return err;
573 }
574