Nonexecutable dynamic areas/pages
Jeffrey Lee (213) 6048 posts |
I’m tempted to have a go at implementing this, as it should be pretty straightforward to get working. However it’s worth giving people a bit of a warning because I’m considering adding it to the access privilege bits – which haven’t been touched since about 1987. E.g. keep bits 0 and 1 as specifying the protection level, and use bit 2 to indicate that it’s nonexecutable. My motivation for implementing it this way is that:
For reference, ARM use 3 bits to specify access privilege in ARMv7, and two bits for two levels of executability control – one ‘XN’ bit for ‘execute never’ and one ‘PXN’ bit for ‘not executable in privileged modes’ (i.e. so that your kernel doesn’t accidentally jump into user code). So if I just implement XN support (PXN isn’t supported by current RISC OS hardware) then that would leave us with one bit left, should we feel the need to expand to the full 3 bits which ARMv7 supports. The only problem I can think of is that there might be some code out there somewhere which will fail horribly if it sees access flags which aren’t in the range 0-3, and so finding a flag bit from elsewhere might be safer. If nobody complains, I guess I’ll just suck it and see! |
Jon Abbott (1421) 2654 posts |
Adding it to the access privilege bits may break most software, as they’re specified as a number and not specific to bits [0.1] (on the Wiki, I’ve not checked the PRM) Only one way to find out. Will changes be required to the Abort handler to deal with any attempted execution? |
Jeffrey Lee (213) 6048 posts |
Yeah, that’s what I’m worried about. Of course it’ll only break software which tries to interpret the values, which I doubt much software will do. Aemulor, Geminus and ADFFS are probably the only ones I can think of, and they should be pretty straightforward to test. And there’s no point having two bits reserved for future expansion if we can’t ever use them for fear of breaking old software!
The current handler should work fine, it’ll come through via the prefetch abort handler, which will just look at the address and go “nope, that’s not in application space” and then pass it on to the environment handler (which will likely just raise an error). Some more detailed error messages might be nice however; one of the things that’s been on the todo list for a while is to make the code look at the IFSR/DFSR and generate an appropriate message for the fault type instead of just a generic “abort on data transfer”, etc. |
Jon Abbott (1421) 2654 posts |
The change shouldn’t affect ADFFS as it doesn’t use OS_Memory for access permissions, it goes direct to L2PT. Aemulor may be affected, although from the docs I’ve read about its implementation, it should have enough control over what’s going on to not need to resort to changing memory permissions to support self-modifying code … You’ll get publicly flogged if you break it though! ;) Do any of the libraries use OS_Memory for access flags? CLib, UnixLib etc. I wouldn’t have though they would, but that’s just a guess. |
Jeffrey Lee (213) 6048 posts |
Looking at recent ARM docs it looks like the possible (AArch32) access permissions are as follows: Long descriptor formatThis is the new page table format that gives full access to large physical addresses (i.e. for >= 4GB of RAM). It’s also the only format capable of supporting logical addresses over 4GB. AP[2] AP[1] XN PXN -> EL1 EL0 0 0 0 0 RWX 0 0 0 1 RW 0 0 1 0 RW 0 0 1 1 RW 0 1 0 0 RWX RWX 0 1 0 1 RW RWX 0 1 1 0 RW RW 0 1 1 1 RW RW 1 0 0 0 R X 1 0 0 1 R 1 0 1 0 R 1 0 1 1 R 1 1 0 0 R X R X 1 1 0 1 R R X 1 1 1 0 R R 1 1 1 1 R R(AP[2] = 'read-only' bit in long descriptor format; AP[1] = 'enable unprivileged access bit' in long descriptor format; XN & PXN are execute-never and privileged execute-never; EL1 = privileged modes, EL0 = unprivileged) That’s 10 unique combinations of access modes. 3 of them overlap with our existing access permissions (0, 2, 3), leaving option 1 (privileged read/write, unprivileged read-only) out in the cold. For the future-thinking, with AArch64 the XN bit is repurposed as ‘unprivileged execute-never’, so you get a couple more combinations. But you also lose a couple of combinations, since pages which are writable at EL0 are always non-executable at EL1. So you still have a total of 10 useful combinations, but only 8 common ones between AArch32 and AArch64. Short descriptor formatThis is the older format we’re currently using. It’s the only one that supports the ‘privileged read/write, unprivileged read-only’ access mode, but it has limited support for physical addresses over 4GB (can only map the high addresses in blocks of 16MB – not very convenient), and limited support for PXN (can only set the flag on entries in the L1 page table – effectively limiting the PXN granularity to 1MB regions) Ignoring XN and PXN for now, there are 3 bits used for access permissions, suggesting a total of 8 combinations. But one is useless (“no access”), one is reserved, and one is a duplicate of another. So you get five useful combinations, four of which we support (our current access permissions), and a fifth which is “privileged read-only, unprivileged inaccessible”. But that mode is also available in the long descriptor format, so in terms of working out the complete set of possible access permissions there’s only one short-descriptor permission worth mentioning here: AP bits set to 010, “privileged read-write, unprivileged read-only” (i.e. the mode which the long-descriptor format doesn’t support). Combining that with XN and PXN gives us the following: XN PXN -> EL1 EL0 0 0 RWX R X 0 1 RW R X 1 0 RW R 1 1 RW R = 3 useful combinations, or 2 if we ignore the tricky PXN bit. SummaryPutting it all together, we have 10 access permissions supported by the long descriptor format, and 2 extra ones added by the short descriptor format. These provide full coverage for our four current permissions, so the total number is 12. That sits happily within the 4 bits we’ve currently got reserved for specifying access permissions, while still leaving a little room for future expansion (e.g. if UXN support gets added to AArch32). |
Jeffrey Lee (213) 6048 posts |
And here’s the full set of permissions, with compatibility information, and potential numbering to fit it in with RISC OS: EL1 EL0 0 RWX RWX 1 RWX R X Short-descriptor only 2 RWX 3 R X R X 4 RW RW ARMv6+ 5 RW R ARMv6+, Short-descriptor only 6 RW ARMv6+ 7 R R ARMv6+ 8 RW RWX ARMv7+, Long-descriptor only (PXN) 9 R R X ARMv7+, Long-descriptor only (PXN) 10 R X ARMv6+ 11 R ARMv6+ |
Jeffrey Lee (213) 6048 posts |
(In case people haven’t worked it out, the scope of this thread has now expanded to cover all the new permissions supported, rather than just the XN bit) Since the availability of the new permissions will differ depending on the hardware, and old OS versions will (probably) fail pretty badly if given a permission that they don’t recognise, I’m thinking that we should actually go down the following route:
That way we can add/remove permissions from the list as we see fit, and the requirement of the program to look up the permission beforehand will take care of the compatibility issues with different machines or old OS versions. (Apart from the fact that our access privilege 1 isn’t supported with the long descriptor format – so if old software tries to use it without checking availability we’ll either have to throw an error or downgrade it to privilege 0) For the permissions query SWI, I’m guessing we’d want at least the following:
|
Jeffrey Lee (213) 6048 posts |
The changes to allow access privileges 4+ to be allocated in a system-dependent manner are now in (actually the changes went in a few days ago but it took me a while to find the time to update the wiki). On ARMv6+ this allows the use of non-executable pages and the “privileged read-only, user none” permissions (although I haven’t yet made the OS use any of these permissions itself) https://www.riscosopen.org/wiki/documentation/show/Memory%20Map%20Page%20Access |