Executables
Posted: Thu Oct 01, 2020 9:48 am
How would my kernel run executables? I want to be binary compatible with Linux.
Load the first part (1kB should suffice) of the file from disk, verify the ELF header (ELF magic and machine class and type, and file type must be ET_EXEC or ET_DYN), set up file mappings according to all the LOAD segments. Set up the stack according to the ELF entry point rules (argc first, then the argv pointers, a NULL, the environment pointers, a NULL, then the aux vectors. Of course, those pointers need to point somewhere, so you need to copy the strings into the address space as well). If a PT_INTERP segment is present, interpret the contents as a file name and load that one as well. Then just start at the entry point.PavelCheckov wrote:How would my kernel run executables?
You need the same syscalls with the same behaviors, and the same VFS, then. And once you have all that, what's the point of your OS?PavelCheckov wrote: I want to be binary compatible with Linux.
That's for the interpreter to do, not the kernel. Separation of concerns. Plus, there are ready-made ELF interpreters out there (e.g. musl, which has one of the simpler ones).iansjack wrote:Don't you also need to do some relocation and load any needed dynamic libraries?
Not necessarily. Loading shared libraries by an interpreter could be a huge security risk. People usually don't think about that, just get on with it, but the truth is, by denying userspace code to map executable pages altogether, you can eliminate a big deal of attack vectors at once. (Not to mention how easy it is to inject a keylogger with LD_PRELOAD.)nullplan wrote:That's for the interpreter to do, not the kernel.
Security theater again. So instead of separating the code that parses the dynamic information from the ELF executable out into a different program, you would prefer to run it at ring 0? As I said, musl has one of the simpler interpreters, and it is plenty complicated enough to give you a headache. I've also looked at the one in glibc, and now I know what horror looks like in C. Imagine running that stuff in your kernel! This is really hard to get right, and almost impossible to get secure. With an interpreter, the only thing that a user can achieve with a prepared executable is to exploit himself, so there it doesn't matter, but if the interpreter is running at ring 0, this changes dramatically. In general, you should have as little stuff in your kernel as possible (but no less).bzt wrote:I agree with @nullplan, except one thing (on that I'm with @iansjack):Not necessarily. Loading shared libraries by an interpreter could be a huge security risk. People usually don't think about that, just get on with it, but the truth is, by denying userspace code to map executable pages altogether, you can eliminate a big deal of attack vectors at once. (Not to mention how easy it is to inject a keylogger with LD_PRELOAD.)nullplan wrote:That's for the interpreter to do, not the kernel.
Let's read that again, shall we?iansjack wrote:The question was how to run executables. Whether the relocation is done by the kernel itself or a user program is irrelevant - it still needs to be done.
And that is what I have answered. Incidentally, relocations do not need to be processed in static executables, so maybe that is a good place to start.PavelCheckov wrote:How would my kernel run executables?
No, of course not. The manner in which the question was asked and the previous posts of the OP did not inspire confidence in me that he appreciates the complexities of what he is asking. A complete answer to his question should probably contain liberal amounts of quotes from the ELF spec, the relevant processor supplements, and the relevant ABI documents. And maybe a link to the wiki page. However, my first answer is enough to get him started, and once he actually has a working static ELF loader, we can continue to more serious detail questions that will undoubtedly come up.iansjack wrote:The simple explanation is not enough to answer the OP' s question.
I am making a 64 bit version of CP/M.You need the same syscalls with the same behaviors, and the same VFS, then. And once you have all that, what's the point of your OS?
Yes. It's more secure if only kernel can map executable code, and it maps those read-only. Some architecture can force this in hardware (from user space a page is either executable or writable, but never both, see ARM's WNX feature). Having the dynamic linker in a user space program means you must allow user space to write pages which will be executed later.nullplan wrote:So instead of separating the code that parses the dynamic information from the ELF executable out into a different program, you would prefer to run it at ring 0?
No it won't reduce risk at all. What improves security is that user space code can't write into memory that will be executed later. Statically linking won't stop a malicious code to put code in a buffer overflow.nullplan wrote:Or just do it like me and mostly link statically. That reduces attack surface like you wouldn't believe.
That's absolutely true.iansjack wrote:Whether the relocation is done by the kernel itself or a user program is irrelevant - it still needs to be done.
And you want to be compatible with Linux of all things? Good luck with that.PavelCheckov wrote:I am making a 64 bit version of CP/M.
You're not listening to my concern: i don't think all that complexity belongs in the kernel. If it is there, it is likely to be buggy, and in the best case someone will stumble over one of those bugs and see their program crash. In the worst case, someone will find an exploit and gain ring 0 access.bzt wrote:Yes. It's more secure if only kernel can map executable code, and it maps those read-only.
False. The only executable pages the dynamic linker needs to map come straight from disk files, and are read-only. Except for textrels (an awful hack, that nobody should ever use, and that is unsupported by most dynamic linkers anyway), the relocation targets are always in writable sections. You know, data. In all the ABIs I have read, there was only ever a single one that used writable code sections, and that was the original PowerPC ELF ABI (used an uninitialized PLT section, to be filled out by the dynlinker at load time), and that one was phased out two decades ago.bzt wrote:Having the dynamic linker in a user space program means you must allow user space to write pages which will be executed later.
You keep pivoting. A post ago, LD_PRELOAD was the big problem, now it's suddenly buffer overflows. You are right, static linking does not address those. You failed to bring them up before. But your regime doesn't put a stop to them, either. Have you ever heard of ROP (return-oriented programming)? No additional executable pages are needed. Once a buffer is overflowed, control flow can be subverted through already existing code to do whatever it is the attacker needs to do. Stack canaries can be helpful in mitigating against those attacks, but your strict no-exec-mapping regime certainly does not help.bzt wrote:No it won't reduce risk at all. What improves security is that user space code can't write into memory that will be executed later. Statically linking won't stop a malicious code to put code in a buffer overflow.
What does your kernel do now? Does it output something to the screen?PavelCheckov wrote:How would my kernel run executables? I want to be binary compatible with Linux.
A dynamic linker is not of a big complexity. Not simple, but not particularly complex either (typically no more than few hundred SLoC).nullplan wrote:You're not listening to my concern: i don't think all that complexity belongs in the kernel.
No, I'm not "pivoting". A post ago I wrote, and I quote: "the truth is, by denying userspace code to map executable pages altogether, you can eliminate a big deal of attack vectors at once. (Not to mention how easy it is to inject a keylogger with LD_PRELOAD.)"nullplan wrote:You keep pivoting. A post ago, LD_PRELOAD was the big problem
Are you serious? What do you think, why did ARM implement WNX permission in hardware if its just a "nonsense"?nullplan wrote:And what is this "It's more secure" nonsense?
...
What attack do you prevent with this?
Code: Select all
The architecture also provides controls bits in the System Control Register (SCTLR_ELx) to make all write-able addresses non-executable.
Excuse me? Are you saying that text relocations are unsupported by dynamic linkers? And that they shouldn't be ever used?nullplan wrote:False. The only executable pages the dynamic linker needs to map come straight from disk files, and are read-only. Except for textrels (an awful hack, that nobody should ever use, and that is unsupported by most dynamic linkers anyway)
Yes, it does help, because the malicious code cannot modify the existing code (rendering all buffer-overflow attacks impossible once and for all), and it cannot return to non-executable data. There's simply only valid, unmodified code to return to.nullplan wrote:but your strict no-exec-mapping regime certainly does not help.
No, they can't overwrite, that's the point! Having the dynamic linker in the kernel means GOT is only writeable by the kernel, and for user space it's read-only. Any attempt to change the GOT from ring 3 would trigger an exception.nullplan wrote:They can overwrite the GOT entry for printf()
It does prevent all attacks you mentioned. I'm sad to inform you that it is you who misunderstands the concept of a "dynlinker-in-kernel".nullplan wrote:And your dynlinker-in-kernel regime also fails to prevent that attack.
We're just going to have to agree to disagree on that one. A dynamic linker is a complex piece of software parsing attacker-controlled data, and it is getting nowhere near my kernel.bzt wrote:A dynamic linker is not of a big complexity. Not simple, but not particularly complex either (typically no more than few hundred SLoC).
Let's not get into that discussion or we'll be here all week.bzt wrote:Did you read my post at all? Or did you just read the upper-case letters at the end and forget about the rest?
Nice evade. You didn't actually answer my question. Nice of ARM to prevent data execution in a centralized way, but x86 has had data execution prevention for a while now as well. And that page doesn't say why you shouldn't allow userspace to map executable pages.bzt wrote:Are you serious? What do you think, why did ARM implement WNX permission in hardware if its just a "nonsense"?
YES! Textrels are an awful hack only present in non-PIC code that has been linked dynamically. PIC code actually manages to put all the relocations into the data section, and the actual code section remains unaltered. This also means that the dynamic linker only has to support a limited number of relocation types, compared to the link editor. Textrels mean you are writing into the text section, which means it cannot be shared between processes anymore. So the one thing shared libraries are trying to do will be undermined by them.bzt wrote:Excuse me? Are you saying that text relocations are unsupported by dynamic linkers? And that they shouldn't be ever used?
No, no, and no. Malicious code already cannot modify existing code. For instance, here I am looking at all Firefox instances on my system, looking for pages that are writable and executable at the same time, and failing to find any:bzt wrote:Yes, it does help, because the malicious code cannot modify the existing code (rendering all buffer-overflow attacks impossible once and for all), and it cannot return to non-executable data. There's simply only valid, unmodified code to return to.
Code: Select all
$ for i in $(pidof firefox), do grep wx /proc/$i/maps; done
$
The GOT is in the same segment as the data section. You cannot write-protect one without write-protecting the other. And write-protecting the data section would be kind of counter productive.bzt wrote:No, they can't overwrite, that's the point! Having the dynamic linker in the kernel means GOT is only writeable by the kernel, and for user space it's read-only. Any attempt to change the GOT from ring 3 would trigger an exception.
Your arrogance astounds me. You fail to even consider that you might be wrong. This was my last reply on the matter.bzt wrote:Think about the things I wrote above. If you still can't see why a user space dynamic linker is bad for security, then I'm sorry, I'm afraid I cannot help you more.