Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
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
22struct wsl_link_reparse_data {
23 __le32 type;
24 char link[];
25};
26
27/* Index entry in $Extend/$Reparse */
28struct 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 */
41static 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 */
79static 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
106static 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 */
134unsigned 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
173unsigned 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 */
217static 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 */
246static 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 */
280static 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);
317put_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 */
330static 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);
373put_rp_inode:
374 iput(rp_inode);
375
376 return err;
377}
378
379/*
380 * Delete a reparse index entry
381 */
382int 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
422out:
423 iput(vi);
424 return err;
425}
426
427/*
428 * Set the reparse data from an extended attribute
429 */
430static 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
487out:
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 */
498int 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 len = ntfs_ucstonls(ni->vol, target, target_len, &utarget, 0);
509 if (len <= 0)
510 return -EINVAL;
511
512 reparse_len = sizeof(struct reparse_point) + sizeof(data->type) + len;
513 reparse = kvzalloc(reparse_len, GFP_NOFS);
514 if (!reparse) {
515 err = -ENOMEM;
516 kfree(utarget);
517 } else {
518 data = (struct wsl_link_reparse_data *)reparse->reparse_data;
519 reparse->reparse_tag = IO_REPARSE_TAG_LX_SYMLINK;
520 reparse->reparse_data_length =
521 cpu_to_le16(sizeof(data->type) + len);
522 reparse->reserved = 0;
523 data->type = cpu_to_le32(2);
524 memcpy(data->link, utarget, len);
525 err = ntfs_set_ntfs_reparse_data(ni,
526 (char *)reparse, reparse_len);
527 kvfree(reparse);
528 if (!err)
529 ni->target = utarget;
530 else
531 kfree(utarget);
532 }
533 return err;
534}
535
536/*
537 * Set reparse data for a WSL special file other than a symlink
538 * (socket, fifo, character or block device)
539 */
540int ntfs_reparse_set_wsl_not_symlink(struct ntfs_inode *ni, mode_t mode)
541{
542 int err;
543 int len;
544 int reparse_len;
545 __le32 reparse_tag;
546 struct reparse_point *reparse;
547
548 len = 0;
549 if (S_ISSOCK(mode))
550 reparse_tag = IO_REPARSE_TAG_AF_UNIX;
551 else if (S_ISFIFO(mode))
552 reparse_tag = IO_REPARSE_TAG_LX_FIFO;
553 else if (S_ISCHR(mode))
554 reparse_tag = IO_REPARSE_TAG_LX_CHR;
555 else if (S_ISBLK(mode))
556 reparse_tag = IO_REPARSE_TAG_LX_BLK;
557 else
558 return -EOPNOTSUPP;
559
560 reparse_len = sizeof(struct reparse_point) + len;
561 reparse = kvzalloc(reparse_len, GFP_NOFS);
562 if (!reparse)
563 err = -ENOMEM;
564 else {
565 reparse->reparse_tag = reparse_tag;
566 reparse->reparse_data_length = cpu_to_le16(len);
567 reparse->reserved = cpu_to_le16(0);
568 err = ntfs_set_ntfs_reparse_data(ni, (char *)reparse,
569 reparse_len);
570 kvfree(reparse);
571 }
572
573 return err;
574}