Replies: 1 comment
-
Hmm, one potential problem I see with this idea is that I assume the MPU for most ARM processors won't let you access memory at a byte level, and will require you to expose at least a page. So if you lease some memory that's not page-aligned, you either can't access it or may expose too much memory. Unless I'm understanding the MPU wrong. |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
The idea, in a nutshell, is to add a syscall to access one task's memory from another task, through the borrowed leases system.
The reference docs say it's not used because only a fault could be delivered on task restart, however I have a use-case where I'd be willing to work with that downside.
(everything below just expands on this idea.)
I have a theoretical use-case for Hubris that involves running on an EFM32PG23 chip, which has 64KB of RAM. The chip is a Armv8M Cortex-M33 with an MPU, so it should run as-is with little if any porting effort.
In order to save memory, and for the ability to execute code, I'm interested in using the memory lease system to directly access the memory of a sending task. This is because I'd like to have one task that decrypts data (which is the only task that has access to a particular key) and verifies a signature, then passed to another task to decode/parse the data, and finally passed to a third task to execute code within that data. This code is signed, but exists external to the compiled code.
(The decode/parse step can be useful because then I can split executable code and non-executable data, i.e. the
text
anddata
of an executable, and keep the latter mapped as RW without X.)I'd like to do so by holding the "actual" memory in the first task, passing it as read-write to the second task, then passing it as read-execute (no write) to the third task. Then a scheduler could kill and re-initialize the executing task once it's complete. This seems like pretty much the safest I can get when executing external (albeit signed) code, in case I mess up signing or have some other bug.
Now granted, this exact idea is mentioned in the reference documentation under BORROW_READ and BORROW_WRITE:
In this case, I am actually perfectly okay with a fault being delivered on accessing invalid memory, and am willing to take on the risks of faults in shared servers.
Additionally in the code, I've noticed that the accessible memory regions for each task are statically defined at compile time, which means as-is, a dynamic remapping wouldn't work. On top of the
regions
field inTaskDesc
, there would need to be an additional field for dynamic maps, and of course that would add to memory usage. For that reason, such a feature might be best behind a feature flag.I'm tempted to add this functionality to Hubris, but out of curiosity, is this functionality that the Hubris devs would be interested in having in their repository, or would it be better to keep such functionality in an external fork? If the latter, do you have any recommendations on how best to implement this?
My current idea is to add a new
BORROW_
syscall which would map the memory in an existing lease from aRECV
call (since that should validate the access), and add a check on task switches (in some way, not sure the best approach for this) to invalidate leases if the leaser's generation increments. At that point, if the lease becomes invalid, then let a fault occur. (As an aside, perhaps there could be another syscall that checks if a lease is still valid, and while that would still be a TOCTOU issue, it could possibly reduce the number of faults that occur. This wouldn't be necessary in my use-case though.)The other unresolved part of my idea is how to work out RWX permissions - read and write is simple, because if you don't have write access then the lease fails. But one of the things I'm hoping for is to enforce W^X, i.e. one task has RW access, then passes it to another task where it immediately becomes RX, then goes back to RW.
At this point I'm thinking of stating that if a lease has read access then it automatically has execute access, but that seems like a huge hack that only works for my specific use-case. There might need to be a fourth permission that's like "effective execute", where you have permission lease memory out as executable to others, but you yourself cannot execute on that memory.
Okay, this post has gotten ridiculously long, but do you have any advice for this idea? Is this a stupid idea that goes strongly against Hubris' design decisions, or is this something worth designing/building?
Thanks!
Beta Was this translation helpful? Give feedback.
All reactions