Repository navigation
sender: resolve --files-from entries under a module subdirectory - #1127
Conversation
A --files-from pull whose source is a subdirectory of a daemon module (rsync://host/mod/subdir/) looked up every listed file at the module root. A missing file was reported as "file has vanished" and the transfer ended with code 24; a same-named file at the module root was sent in its place. With --remove-source-files, the sent files were then kept with "Skipping sender remove for changed file". The sender opens each file from the module root with secure_relative_open(), rebuilding the path as F_PATHNAME + "/" + f_name, and secure_sender_parent_fd() rebuilds the parent the same way. A --files-from entry has no F_PATHNAME: like any relative pathname it is relative to orig_dir, the transfer's source directory, which here is a module subdirectory. Dropping orig_dir made both look under mod/<entry> instead of mod/subdir/<entry>. Build the module-relative path in one place, module_relative_path(), and prefix a missing or relative F_PATHNAME with orig_dir's module-relative part, as change_pathname() does when it chdirs. orig_dir is no longer static in flist.c so sender.c can read it. Add daemon-files-from-subdir_test.py, which fails without the fix.
a8027cd to
a16fa8b
Compare
|
Quite unfortunate that this was submitted publicly..... private reporting does exist for a reason :/ |
Hey! Ouch, I’m sorry about that! It wasn’t my intention honestly, I’d rather close the PR and kill the repo (or create a new one) for submitting this in a cleaner way? I wasn’t aware at all that this caused a security issue 😅 I was more concerned about it returning incorrect results, as I was more focused on data integrity at the time. Now that you explain the above it makes sense to me. I’ll apply your changes though, but feel free to tell me how else do you want me to proceed, I’ll take this as learning experience and be more careful next time! 🙏🏻 |
|
Nope its all good. This can be kept public. But in the future, I do tend to prefer to lean on the side of caution with security-adjacent work. Can always then move it to public if deemed unnecessary :D -- in this case, its more so that our documentation guarantees certain security guarantees and this bypasses them. Much appreciated for the report and work itself. |
No problem, actually thank you! Really appreciate it! 🙏🏻 and noted! I'll make sure to go through private reporting first if I ever find anything that's security-adjacent 😅 I also pushed the requested changes (filter in the test fixture), so it should be ready for another look whenever you have time. Thanks again for being cool with it! 🙏🏻 |
|
@darkguy2008 For future note as well. The primary concern is data integrity however it is technically an authorisation bypass just very narrow and configuration-dependent + the attacker would require access to the module already and I feel you have bigger issues at that point so not worth a CVE but when it comes to edge-cases that are security-adjacent (even data integrity) its always good to submit privately first and then pivot to public if needbe in these cases as it is somewhat common for these edge/corner cases to explode and become worse (and in other projects this could legitimately be worth a low/medium CVE) -- of course dont be overly cautious with things that really aren't CVE-worthy but its always good to be safe. |
Awesome!! No problem, glad to have been able to contribute and thank you for being cool with it as well 😁 it feels good to be able to contribute now to great projects like this one!
Yeah that's what I was thinking too, if you have access to the module then you've got bigger problems to solve 😅, but it was a good catch! Understood though, if I ever need to do the same again here or maybe in other projects too, I'll take your advice. Appreciate you for this experience! |
Hey! This PR came from an app I'm working on, which is some kind of game console. While using rsync there, I found that when I pull from a subdirectory of a daemon module with
--files-from, rsync looks for each file at the module root instead of in that subdirectory. Depending on what's there, the files are reported as vanished, or a same-named file from the module root gets sent instead.Here's the description generated by my LLM on this. I've also made sure that it ran all the tests to completion, all green, and A/B testing to make sure that, without this fix, the problem actually arises and it's fixed with this PR. Let me know if you need me to do anything else to make sure this PR can be merged. Thank you in advance!
Problem
This is the kind of pull that breaks:
subdir/. This bypasses the module'sfilter: withfilter = + /subdir/*** - *, the excluded module-root file is sent.--remove-source-files, if that module-root file has the same size and mtime as the listed one, it is deleted, even when the module filter excludes it. Otherwise the sent files are kept withSkipping sender remove for changed file, and rsync exits with code 23.The same files pulled from the module root work:
Seen with rsync 3.5.1 on Fedora 44, and reproduced on current master.
Root cause
In
send_files(), thesecure_relpath_active()branch opens each file from the module root withsecure_relative_open(). It rebuilds the path asF_PATHNAME + "/" + f_name.secure_sender_parent_fd(), used by--remove-source-files, rebuilds the parent directory the same way.A
--files-fromentry has noF_PATHNAME. Like any relative pathname, it is relative toorig_dir, the transfer's source directory. In this case that is the module subdirectory. Leaving outorig_dirmakes both look undermod/<entry>instead ofmod/subdir/<entry>.Fix
Prefix both path builds with the module-relative part of
orig_dir, through a one-line helper,orig_subdir().change_pathname()uses the same rule when it chdirs: a relative dir is resolved fromorig_dir. An absoluteF_PATHNAME(a module withpath = /, #897) is left unchanged.orig_diris no longerstaticin flist.c, so sender.c can read it.Verification
testsuite/daemon-files-from-subdir_test.pypullsfile.txtwith--remove-source-filesfrommod/subdir/of a module withfilter = + /subdir/*** - *. A same-named, same-size, same-mtimefile.txtsits at the module root, excluded by the filter. The test checks that the subdirectory's file is sent and removed, and that the excluded file is left in place. On Fedora 44 (aarch64 and x86_64) it fails on master (the excluded file is sent) and passes with the fix. It also fails with only thesend_files()half of the fix: the right file is sent, but--remove-source-fileskeeps it and deletes the excluded module-root file instead.make checkpasses with the fix on Fedora 44 aarch64: 302 passed, 65 skipped, 0 failed.$1:Master prints
subdir source: exit 24. With the fix it printsexit 0.Related
--files-fromconfinement for local and SSH transfers, not daemon module subdirectories.