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
Generate frame tables in data section to improve support for DLLs and PIEs #1323
Conversation
The frametable contains absolute pointers into the code, which require relocation in shared libraries and also in position-independent executables (PIE). Before this commit, the frametable was put in the readonly data section (rodata), which is part of the text segment. In shared libraries and PIEs, relocations in the text segment are undesirable (they make the text segment writable, at least temporarily) and are flagged as warnings or errors by various tools (Debian's lintian package checker; the --warn-shared-textrel option of GNU ld; etc). This commit puts the frametable in the (read-write) data section (.data), like in the AMD64 port for example. In PowerPC 64-bit mode, this is enough to produce .so files and PIE executables that contain no relocations in the text segment. In PowerPC 32-bit mode there remains relocations in the text segment, but that was expected because the code we generate is not position-independent (PIC).
This is the System-Z analogue of commit 24980d3 for PowerPC. With this commit ocamlopt produces .so shared libraries and PIE relocatable executables that contain no relocations in the text segment.
With this change, the generated libasmrun_shared.so is a "pure" shared library without relocations in the text segment.
This looks correct to me. I checked the s390x instructions in the new macros and that there weren't any accidental clobberings of OK to be merged after a |
Thanks for the prompt review! Changes entry added. |
So this puts the relocations in |
@cfcs : I don't know what I don't think we lose in security by having frame tables read-write instead of read-only: any malicious code that can write to the other read-write data of an OCaml process already has full control of the process; having write access to the frame table gives the attacker no additional possibilities, as far as I can see. The question here is more a question of efficiency, trying to reduce the amount of dynamic loading work and increase the opportunities for sharing the text segment between several instances of the process or DLL. |
@xavierleroy: Yes,
This second half of my comment is a bit off-topic, but I'd like to share an example from an application I'm working on now: A ".native" application compiled with the opam switch
By default it has these
Having the I have not looked into the other I would love to see an OCaml ecosystem that is more resistant to these attacks as I believe the OCaml language has the potential to be used for developing secure software, and I would like to contribute towards this end. I come from a background of IT security / penetration testing and have experience with exploiting memory corruption flaws like these, but I have very little experience with the |
I agree Your example is a big surprise to me because OCaml doesn't need an executable heap (and does nothing to make the heap executable), nor an executable stack (and does the
On mine (Ubuntu 16.04 x86-64) it reports no If you still see |
@xavierleroy that gave me no |
Yes, hand-written assembly files easily lead to executable stacks, just because they lack the magic "note" that tells the linker to keep the stack nonexecutable. I have no idea what could cause an executable heap, however. |
The
ocamlopt
compiler produces tables that describe the structure of stack frames. These are called the frame tables, and they contain pointers (absolute addresses) into the machine code.Currently, the frame table data is put in the read-only data section ".rodata" for the PowerPC and System-Z / s390x target architectures, and in the read-write data section ".data" for the other ports.
For statically-linked executables, the read-only data section is a good choice because the frame tables do not change during execution. What happens is that the static linker puts the code and the read-only data together in a read-only "text" segment, which can be shared between several concurrent runs of the program.
For the same reason, putting the frame tables in the read-only data section is a poor choice for shared libraries (DLLs), because the code pointers contained in the frame table need to be relocated by the dynamic loader. So, the dynamic loader needs write access to the "text" segment, which can no longer be shared between several users of the DLL, making dynamic loading slower and possibly reducing security.
The same concerns arise for position-independent executables (PIEs). These are statically-linked executables that are loaded and run at randomly-chosen code and data addresses, as part of ASLR (address space layout randomization), a popular security measure. Like DLLs, PIEs undergo relocation of symbols, and if the text segment contains relocations, it can no longer be shared, it takes longer to start, and it could weaken security. PIEs are already or are becoming the standard, e.g. in MacOS, OpenBSD, and Ubuntu 17.04.
For those reasons, relocations in the text segment (code sections and readonly data sections) should be avoided. They are flagged as warnings or errors by tools such as Debian's
lintian
package checker and the--warn-shared-textrel
option of GNU ld.Consequently, ocamlopt should stop emitting its frame tables in the readonly data section and should consistently emit them in the data section. This is exactly what this PR does. There is also a small improvement to the System-Z port to make the
libasmrun_shared.so
shared library completely relocatable.With this PR, the four 64-bit ports of OCaml (x86-64, ARM64, PowerPC64 and System-Z) produce "proper" shared libraries and executables that can be "properly" linked as PIEs (e.g. via
ocamlopt -ccopt -pie
), where "proper" means "with no relocations in the text segment".The three 32-bit ports of OCaml (x86-32, ARM, PowerPC) have bigger issues with PIEs that will probably be discussed in another PR.