Here is a redefinition of trampfd based on review comments.
I wanted to address dynamic code in 3 different ways:
Remove the need for dynamic code where possible
--------------------------------------------------------------------
If the kernel itself can perform the work of some dynamic code,
then
the code can be replaced by the kernel.
This is what I implemented in the patchset. But reviewers
objected
to the performance impact. One trip to the kernel was needed for
each
trampoline invocation. So, I have decided to defer this
approach.
Convert dynamic code to static code where possible
----------------------------------------------------------------------
This is possible with help from the kernel. This has no
performance
impact and can be used in libffi, GCC nested functions, etc. I
have
described the approach below.
Deal with code generation
-----------------------------------
For cases like generating JIT code from Java byte code, I wanted
to
establish a framework. However, reviewers felt that details are
missing.
Should the kernel generate code or should it use a user-level
code generator?
How do you make sure that a user level code generator can be
trusted?
How would the communication work? ABI details? Architecture
support?
Support for different types - JIT, DBT, etc?
I have come to the conclusion that this is best done separately.
My main interest is to provide a way to convert dynamic code such as
trampolines to static code without any special architecture support.
This
can be done with the kernel's help. Any code that gets written in
the future can conform to this as well.
So, in version 2 of the Trampfd RFC, I would like to simplify
trampfd and
just address item 2. I will reimplement the support in libffi and
present it.
Convert dynamic code to static code
------------------------------------------------
One problem with dynamic code is that it cannot be verified or
authenticated
by the kernel. The kernel cannot tell the difference between genuine
dynamic
code and an attacker's code. Where possible, dynamic code should be
converted
to static code and placed in the text segment of a binary file. This
allows
the kernel to verify the code by verifying the signature of the
file.
The other problem is using user-level methods to load and execute
dynamic code
can potentially be exploited by an attacker to inject his code and
have it be
executed. To prevent this, a system may enforce W^X. If W^X is
enforced
properly, genuine dynamic code will not be able to run. This is
another
reason to convert dynamic code to static code.
The issue in converting dynamic code to static code is that the data
is
dynamic. The code does not know before hand where the data is going
to be
at
runtime.
Some architectures support PC-relative data references. So, if you
co-locate
code and data, then the code can find the data at runtime. But this
is not
supported on all architectures. When supported, there may be
limitations to
deal with. Plus you have to take the trouble to co-locate code and
data.
And, to deal with W^X, code and data need to be in different pages.
All architectures must be supported without any limitations.
Fortunately,
the kernel can solve this problem quite easily. I suggest the
following:
Convert dynamic code to static code like this:
- Decide which register should point to the data that the code
needs.
Call it register R.
- Write the static code assuming that R already points to the
data.
- Use trampfd and pass the following to the kernel:
- pointers to the code and data
- the name of the register R
The kernel will write the following instructions in a trampoline
page
mapped into the caller's address space with R-X.
- Load the data address in register R
- Jump to the static code
Basically, the kernel provides a trampoline to jump to the user's
code
and returns the kernel-provided trampoline's address to the user.
It is trivial to implement a trampoline table in the trampoline page
to
conserve memory.
Issues raised previously
-------------------------------
I believe that the following issues that were raised by reviewers is
not
a problem in this scheme. Please rereview.
- Florian mentioned the libffi trampoline table. Trampoline
tables can be
implemented in this scheme easily.
- Florian mentioned stack unwinders. I am not an expert on
unwinders.
But I don't see an issue with unwinders.
- Mark Rutland mentioned Intel's CET and CFI. Don't see a
problem there.
- Mark Rutland mentioned PAC+BTI on ARM64.
Don't see a problem there.
If I have missed addressing any previously raised issue, I
apologize.
Please let me know.
Thanks!
Madhavan