Skip to content

Mpr/fix rm remote #17149

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 26, 2025
Merged

Mpr/fix rm remote #17149

merged 2 commits into from
Apr 26, 2025

Conversation

Josverl
Copy link
Contributor

@Josverl Josverl commented Apr 17, 2025

Summary

Removes the risk of inadvertently deleting files from the host by preventing the deletion of files from the host via rm -r on the /remote vfs as reported in #17147.

Testing

The PR adds a test for rm -r on the /remote vfs to ensure that the deletion of files from the host is prevented.
Tested manually on Windows.

Trade-offs and Alternatives

The fix only addresses the risk via rm -r as that has a potentially large impact, and not with just the rm command that has been in use for quite some time.
The same safeguard could be added to the `rm' command.

Fixes: #17147

Copy link

Code size report:

   bare-arm:    +0 +0.000% 
minimal x86:    +0 +0.000% 
   unix x64:    +0 +0.000% standard
      stm32:    +0 +0.000% PYBV10
     mimxrt:    +0 +0.000% TEENSY40
        rp2:    +0 +0.000% RPI_PICO_W
       samd:    +0 +0.000% ADAFRUIT_ITSYBITSY_M4_EXPRESS
  qemu rv32:    +0 +0.000% VIRT_RV32

@AJMansfield
Copy link
Contributor

AJMansfield commented Apr 23, 2025

The code seems like a correct implementation of the stated feature -- in that it gates this behind state.transport.mounted, so it won't make rm -r behave unpredictably when a user happens to have a directory by that name for some other reason -- but it does still duplicate the assumption that the path the host filesystem is mounted at will always be /remote/.

There should only be a single source of truth for what that path's name is. Even if it's ultimately still a hardcoded string, it should be one string in one place -- not a first copy of that string in the code setting up the mount, and a second copy of that string in the rm check.

This could be done dynamically using vfs.mount() from #16939 (included in 1.25.0); though to keep compatibility with older firmware versions that might need to be augmented with some other bookkeeping strategy.

@Josverl
Copy link
Contributor Author

Josverl commented Apr 23, 2025

I would be more than happy to catch the 98% of cases where the remote vfs is created through mpremote,
and leave the 2% case of very advanced users, that create their own vfs and backing host service, to their own devices.

The current Dual SOT is the __mount() method that is part of a "string_embedded" micropython script in transport_serial.py

doing an AST parse of that to extract "/remote" would be a bit much IMO,
I do not think that an f-string would be that much clearer either .

A KISS solution would be to add a FS_HOOK_MOUNT = "/remote" , or a class attribute SerialTransport.fs_hook_mount with a comment and a reference to transport_serial.py#L748-L750

@AJMansfield
Copy link
Contributor

A KISS solution would be to add a FS_HOOK_MOUNT = "/remote" , or a class attribute SerialTransport.fs_hook_mount with a comment and a reference to transport_serial.py#L748-L750

Either of those seem pretty reasonable to me.

@AJMansfield
Copy link
Contributor

AJMansfield commented Apr 23, 2025

After some thought, the approach I'd advocate would be to modify __mount to have it set a global variable containing the mount info, e.g.:

def __mount():
    global __mount_MOUNTPOINT
    __mount_MOUNTPOINT = (RemoteFS(RemoteCommand()), '/remote')
    os.mount(*__mount_MOUNTPOINT)
    os.chdir(__mount_MOUNTPOINT[1])

Then, for the host-mount check in rm, after verifying state.transport.mounted you can read back that variable with state.transport.exec("__mount_MOUNTPOINT[1]"), and derive the path to exclude from that.

(Regarding making it a tuple, the idea there is to make it polymorphic with the entries returned by vfs.mount(). Not sure exactly what use that polymorphism could eventually see -- but setting the stage to be able to e.g. easily test if that exact filesystem is really still mounted with __mount_MOUNTPOINT in vfs.mount() seems like a better design than storing only the path name.)

@AJMansfield
Copy link
Contributor

AJMansfield commented Apr 23, 2025

The fix only addresses the risk via rm -r as that has a potentially large impact, and not with just the rm command that has been in use for quite some time.
The same safeguard could be added to the `rm' command.

Safeguarding this on the command-issuer side feels like a somewhat backwards approach to the problem, though. The unix-like idiom to use for this type of protection would be to bail out with an EROFS 'Read-only file system' error or similar, at the filesystem layer; and with a -f/--force flag that circumvents this.

This error could potentially be triggered on the device side inside the fs hook. To do that, would mean needing to make the presence or absence of --force available on the device, which is doable. Perhaps not this implementation strategy specifically, but on the host side, something like:

@contextlib.contextmanager
def fs_flags_set_on_remote(state, args):
    state.transport.exec(f'__mpremote_args={vars(args)!r}')
    try:
        yield
    finally:
        state.transport.exec('del __mpremote_args')


with fs_flags_set_on_remote(state, args):
    ... # actual command functions

Then in the RemoteFS class in the hook, use that to determine if we're in an mpremote command context and trigger the error from the actual removal operations themselves:

class RemoteFS:
    ...

    def _err_if_unforced(err, path):
        global __mpremote_args
        try:
            if __mpremote_args['force'] is False:
                raise OSError(err, path)
        except NameError:
            pass # still allow other programmatic removal

    def remove(self, path):
        self._err_if_unforced(30, path) # errno.EROFS
        ...
    
    def rmdir(self, path):
        self._err_if_unforced(30, path) # errno.EROFS
        ...

This could also be added to open to stop another less-common but still undesirable snag: the way mpremote mount ./local/ + fs cp ./local/a.py :/remote/a.py has the potential to truncate and lose the contents of a.py.

    def open(self, path, mode):
        if any(flag in mode for flag in 'wxa+'):
            self._err_if_unforced(errno.EROFS, path)
        ...

Though, that still wouldn't prevent the truncation issue on the reverse operation (i.e. mpremote mount ./local/ + fs cp :/remote/a.py ./local/a.py).
Maybe it'd be better to push this back even further to the mpremote side where it answers the hook's commands? Since that would create an opportunity to compare the actual resolved paths for the fs command and filesystem operation to stop that copy error; and it would mean we could generate much more helpful error messages about what the equivalent host command would be if that's really what you want:

$ mpremote mount ./local/ + fs cp :/remote/a.py ./local/a.py
mpremote: cp: ':/remote/a.py' and './local/a.py' are the same file
$ mpremote mount ./local/ + fs cp :/a.py :/remote/a.py
mpremote: cp: ':/remote/a.py' is a host file, use 'mpremote cp :/a.py ./local/a.py'
$ mpremote mount ./local/ + fs cp :/remote/a.py :/remote/b.py
mpremote: cp: ':/remote/a.py' and ':/remote/b.py' are host files, use 'cp ./local/a.py ./local/b.py'
$ mpremote mount ./local/ + fs rm :/remote/a.py
mpremote: rm: ':/remote/a.py' is a host file, use 'rm ./local/a.py'

@dpgeorge dpgeorge added the tools Relates to tools/ directory in source, or other tooling label Apr 24, 2025
Copy link

codecov bot commented Apr 24, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 98.54%. Comparing base (dc46cf1) to head (6406afb).
Report is 2 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master   #17149   +/-   ##
=======================================
  Coverage   98.54%   98.54%           
=======================================
  Files         169      169           
  Lines       21890    21890           
=======================================
  Hits        21572    21572           
  Misses        318      318           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@dpgeorge
Copy link
Member

I'd advocate to keep this change/fix simple, and the PR here is pretty simple.

Although eliminating the duplicated /remote path would be good, something simple like the following suggestion from above sounds reasonable:

A KISS solution would be to add a FS_HOOK_MOUNT = "/remote" , or a class attribute SerialTransport.fs_hook_mount with a comment and a reference to transport_serial.py#L748-L750

@Josverl Josverl force-pushed the mpr/fix_rm_remote branch from 7e5b586 to 4b72b8f Compare April 25, 2025 20:20
@Josverl
Copy link
Contributor Author

Josverl commented Apr 25, 2025

Updated to use SerialTransport.fs_hook_mount="/remote" for all references to the mountpoint as used by mpremote.

Josverl added 2 commits April 26, 2025 16:07
Signed-off-by: Jos Verlinde <jos_verlinde@hotmail.com>
Removes the risk of inadvertently deleting files on the host by preventing
the deletion of files via `rm -r` on the `/remote` vfs mount point.

Fixes issue micropython#17147.

Signed-off-by: Jos Verlinde <jos_verlinde@hotmail.com>
@dpgeorge dpgeorge force-pushed the mpr/fix_rm_remote branch from 4b72b8f to 6406afb Compare April 26, 2025 06:08
@dpgeorge dpgeorge merged commit 6406afb into micropython:master Apr 26, 2025
66 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
tools Relates to tools/ directory in source, or other tooling
Projects
None yet
Development

Successfully merging this pull request may close these issues.

"mpremote -rv :/" can remove files from host
3 participants
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy