[COMMIT osv master] Merge 'Dynamic linker: support loading and processing statically linked executables' from WALDEMAR KOZACZUK

9 views
Skip to first unread message

Commit Bot

unread,
Aug 31, 2023, 2:51:47 AM8/31/23
to osv...@googlegroups.com, Nadav Har'El
From: Nadav Har'El <n...@scylladb.com>
Committer: Nadav Har'El <n...@scylladb.com>
Branch: master

Merge 'Dynamic linker: support loading and processing statically linked executables' from WALDEMAR KOZACZUK

The two commits provide necessary modifications to the OSv dynamic linker to support loading and processing statically linked executables.

Please note these changes are NOT enough to make OSv run statically linked executables.

Closes #1253

* github.com:cloudius-systems/osv:
dynamic linker: support loading and processing static ELF
dynamic linker: rename is_executable to is_dynamically_linked_executable

---
diff --git a/arch/aarch64/arch-elf.cc b/arch/aarch64/arch-elf.cc
--- a/arch/aarch64/arch-elf.cc
+++ b/arch/aarch64/arch-elf.cc
@@ -66,7 +66,7 @@ bool object::arch_relocate_rela(u32 type, u32 sym, void *addr,
if (sym) {
auto sm = symbol(sym);
ulong tls_offset;
- if (sm.obj->is_executable()) {
+ if (sm.obj->is_dynamically_linked_executable()) {
// If this is an executable (pie or position-dependant one)
// then the variable is located in the reserved slot of the TLS
// right where the kernel TLS lives
@@ -119,7 +119,7 @@ void object::arch_relocate_tls_desc(u32 sym, void *addr, Elf64_Sxword addend)
ulong tls_offset;
if (sym) {
auto sm = symbol(sym);
- if (sm.obj->is_executable() || sm.obj->is_core()) {
+ if (sm.obj->is_dynamically_linked_executable() || sm.obj->is_core()) {
// If this is an executable (pie or position-dependant one)
// then the variable is located in the reserved slot of the TLS
// right where the kernel TLS lives
@@ -163,7 +163,7 @@ void object::prepare_initial_tls(void* buffer, size_t size,

void object::prepare_local_tls(std::vector<ptrdiff_t>& offsets)
{
- if (!_static_tls && !is_executable()) {
+ if (!_static_tls && !is_dynamically_linked_executable()) {
return;
}

diff --git a/arch/aarch64/arch-switch.hh b/arch/aarch64/arch-switch.hh
--- a/arch/aarch64/arch-switch.hh
+++ b/arch/aarch64/arch-switch.hh
@@ -120,7 +120,7 @@ void thread::setup_tcb()
assert(obj);
user_tls_size = obj->initial_tls_size();
user_tls_data = obj->initial_tls();
- if (obj->is_executable()) {
+ if (obj->is_dynamically_linked_executable()) {
executable_tls_size = obj->get_tls_size();
}
}
diff --git a/arch/x64/arch-elf.cc b/arch/x64/arch-elf.cc
--- a/arch/x64/arch-elf.cc
+++ b/arch/x64/arch-elf.cc
@@ -138,7 +138,7 @@ bool object::arch_relocate_rela(u32 type, u32 sym, void *addr,
if (sym) {
auto sm = symbol(sym);
ulong tls_offset;
- if (sm.obj->is_executable()) {
+ if (sm.obj->is_dynamically_linked_executable()) {
// If this is an executable (pie or position-dependant one)
// then the variable is located in the reserved slot of the TLS
// right where the kernel TLS lives
@@ -202,7 +202,7 @@ void object::prepare_initial_tls(void* buffer, size_t size,

void object::prepare_local_tls(std::vector<ptrdiff_t>& offsets)
{
- if (!_static_tls && !is_executable()) {
+ if (!_static_tls && !is_dynamically_linked_executable()) {
return;
}

diff --git a/arch/x64/arch-switch.hh b/arch/x64/arch-switch.hh
--- a/arch/x64/arch-switch.hh
+++ b/arch/x64/arch-switch.hh
@@ -210,7 +210,7 @@ void thread::setup_tcb()
assert(obj);
user_tls_size = obj->initial_tls_size();
user_tls_data = obj->initial_tls();
- if (obj->is_executable()) {
+ if (obj->is_dynamically_linked_executable()) {
executable_tls_size = obj->get_tls_size();
aligned_executable_tls_size = obj->get_aligned_tls_size();
}
diff --git a/core/elf.cc b/core/elf.cc
--- a/core/elf.cc
+++ b/core/elf.cc
@@ -123,7 +123,7 @@ object::object(program& prog, std::string pathname)
, _initial_tls_size(0)
, _dynamic_table(nullptr)
, _module_index(_prog.register_dtv(this))
- , _is_executable(false)
+ , _is_dynamically_linked_executable(false)
, _init_called(false)
, _eh_frame(0)
, _visibility_thread(nullptr)
@@ -249,9 +249,6 @@ const char * object::symbol_name(const Elf64_Sym * sym) {
}

void* object::entry_point() const {
- if (!_is_executable) {
- return nullptr;
- }
return _base + _ehdr.e_entry;
}

@@ -366,13 +363,13 @@ void object::set_base(void* base)
[](const Elf64_Phdr* a, const Elf64_Phdr* b)
{ return a->p_vaddr < b->p_vaddr; });

- if (!is_core() && is_non_pie_executable()) {
- // Verify non-PIE executable does not collide with the kernel
+ if (!is_core() && !is_pic()) {
+ // Verify non-PIC executable ((aka position dependent)) does not collide with the kernel
if (intersects_with_kernel(p->p_vaddr) || intersects_with_kernel(q->p_vaddr + q->p_memsz)) {
- abort("Non-PIE executable [%s] collides with kernel: [%p-%p] !\n",
+ abort("Non-PIC executable [%s] collides with kernel: [%p-%p] !\n",
pathname().c_str(), p->p_vaddr, q->p_vaddr + q->p_memsz);
}
- // Override the passed in value as the base for non-PIEs (Position Dependant Executables)
+ // Override the passed in value as the base for non-PICs (Position Dependant Executables)
// needs to be set to 0 because all the addresses in it are absolute
_base = 0x0;
} else {
@@ -484,7 +481,7 @@ void object::process_headers()
_dynamic_table = reinterpret_cast<Elf64_Dyn*>(_base + phdr.p_vaddr);
break;
case PT_INTERP:
- _is_executable = true;
+ _is_dynamically_linked_executable = true;
break;
case PT_NOTE: {
if (phdr.p_memsz < 16) {
@@ -536,10 +533,10 @@ void object::process_headers()
abort("Unknown p_type in executable %s: %d\n", pathname(), phdr.p_type);
}
}
- if (!is_core() && _ehdr.e_type == ET_EXEC && !_is_executable) {
- abort("Statically linked executables are not supported!\n");
+ if (!is_core() && is_statically_linked()) {
+ abort("Statically linked executables are not supported yet!\n");
}
- if (_is_executable && _tls_segment) {
+ if (_is_dynamically_linked_executable && _tls_segment) {
auto app_tls_size = get_aligned_tls_size();
ulong pie_static_tls_maximum_size = &_pie_static_tls_end - &_pie_static_tls_start;
if (app_tls_size > pie_static_tls_maximum_size) {
@@ -600,6 +597,12 @@ void object::fix_permissions()
make_text_writable(false);
}

+ //Full RELRO applies to dynamically linked executables only
+ if (is_statically_linked()) {
+ return;
+ }
+
+ //Process GNU_RELRO segments only to make GOT and others read-only
for (auto&& phdr : _phdrs) {
if (phdr.p_type != PT_GNU_RELRO)
continue;
@@ -888,6 +891,9 @@ constexpr Elf64_Versym old_version_symbol_mask = Elf64_Versym(1) << 15;

Elf64_Sym* object::lookup_symbol_old(const char* name)
{
+ if (!dynamic_exists(DT_SYMTAB)) {
+ return nullptr;
+ }
auto symtab = dynamic_ptr<Elf64_Sym>(DT_SYMTAB);
auto strtab = dynamic_ptr<char>(DT_STRTAB);
auto hashtab = dynamic_ptr<Elf64_Word>(DT_HASH);
@@ -917,6 +923,9 @@ dl_new_hash(const char *s)

Elf64_Sym* object::lookup_symbol_gnu(const char* name, bool self_lookup)
{
+ if (!dynamic_exists(DT_SYMTAB)) {
+ return nullptr;
+ }
auto symtab = dynamic_ptr<Elf64_Sym>(DT_SYMTAB);
auto strtab = dynamic_ptr<char>(DT_STRTAB);
auto hashtab = dynamic_ptr<Elf64_Word>(DT_GNU_HASH);
@@ -1019,6 +1028,9 @@ dladdr_info object::lookup_addr(const void* addr)
if (addr < _base || addr >= _end) {
return ret;
}
+ if (!dynamic_exists(DT_STRTAB)) {
+ return ret;
+ }
ret.fname = _pathname.c_str();
ret.base = _base;
auto strtab = dynamic_ptr<char>(DT_STRTAB);
@@ -1068,6 +1080,9 @@ static std::string dirname(std::string path)

void object::load_needed(std::vector<std::shared_ptr<object>>& loaded_objects)
{
+ if (!dynamic_exists(DT_NEEDED)) {
+ return;
+ }
std::vector<std::string> rpath;

std::string rpath_str;
@@ -1263,7 +1278,7 @@ void object::init_static_tls()
if (obj->is_core()) {
continue;
}
- if (obj->is_executable()) {
+ if (obj->is_dynamically_linked_executable()) {
obj->prepare_local_tls(_initial_tls_offsets);
elf_debug("Initialized local-exec static TLS for %s\n", obj->pathname().c_str());
}
@@ -1462,7 +1477,7 @@ program::load_object(std::string name, std::vector<std::string> extra_path,
osv::rcu_dispose(old_modules);
ef->load_segments();
ef->process_headers();
- if (!ef->is_non_pie_executable())
+ if (ef->is_pic())
_next_alloc = ef->end();
add_debugger_obj(ef.get());
loaded_objects.push_back(ef);
diff --git a/include/osv/elf.hh b/include/osv/elf.hh
--- a/include/osv/elf.hh
+++ b/include/osv/elf.hh
@@ -378,9 +378,9 @@ public:
size_t initial_tls_size() { return _initial_tls_size; }
void* initial_tls() { return _initial_tls.get(); }
void* get_tls_segment() { return _tls_segment; }
- bool is_non_pie_executable() { return _ehdr.e_type == ET_EXEC; }
+ bool is_pic() { return _ehdr.e_type != ET_EXEC; }
std::vector<ptrdiff_t>& initial_tls_offsets() { return _initial_tls_offsets; }
- bool is_executable() { return _is_executable; }
+ bool is_dynamically_linked_executable() { return _is_dynamically_linked_executable; }
ulong get_tls_size();
ulong get_aligned_tls_size();
void copy_local_tls(void* to_addr);
@@ -415,6 +415,7 @@ private:
void prepare_local_tls(std::vector<ptrdiff_t>& offsets);
void alloc_static_tls();
void make_text_writable(bool flag);
+ bool is_statically_linked() { return !_is_dynamically_linked_executable && _ehdr.e_entry; }
protected:
program& _prog;
std::string _pathname;
@@ -435,7 +436,7 @@ protected:
Elf64_Dyn* _dynamic_table;
ulong _module_index;
std::unique_ptr<char[]> _section_names_cache;
- bool _is_executable;
+ bool _is_dynamically_linked_executable;
bool is_core();
bool _init_called;
void* _eh_frame;
@@ -462,7 +463,7 @@ protected:
bool arch_relocate_jump_slot(symbol_module& sym, void *addr, Elf64_Sxword addend);
void arch_relocate_tls_desc(u32 sym, void *addr, Elf64_Sxword addend);
size_t static_tls_end() {
- if (is_core() || is_executable()) {
+ if (is_core() || _is_dynamically_linked_executable) {
return 0;
}
return _static_tls_offset + get_tls_size();

Nadav Har'El

unread,
Sep 10, 2023, 5:24:43 AM9/10/23
to Nadav Har'El, osv...@googlegroups.com, Waldek Kozaczuk
When I try to run the default scripts/build (the Lua shell) on Fedora 38 (with https://github.com/cloudius-systems/osv/pull/1257),
I get the error message added in this patch:

$ scripts/run.py
OSv v0.57.0-61-g72ed41c6
eth0: 192.168.122.15
Booted up in 156.82 ms
Cmdline: /cli

Statically linked executables are not supported yet!

Why does it think the Lua executable is statically linked?

--
Nadav Har'El
n...@scylladb.com

Waldek Kozaczuk

unread,
Sep 10, 2023, 3:54:15 PM9/10/23
to OSv Development
Hi,

Could you please run 'readelf -W -l ' against this executable? Maybe it is a statically linked executable.

It is determined by this expression:

bool is_statically_linked() { return !_is_dynamically_linked_executable && _ehdr.e_entry; }

and _is_dynamically_linked_executable is set to true if there is PT_INTERP header.

Nadav Har'El

unread,
Sep 19, 2023, 7:54:00 AM9/19/23
to Waldek Kozaczuk, OSv Development
On Sun, Sep 10, 2023 at 10:54 PM Waldek Kozaczuk <jwkoz...@gmail.com> wrote:
Hi,

Could you please run 'readelf -W -l ' against this executable? Maybe it is a statically linked executable.

In the debugger I see:


1  0x0000000040234f20 in abort (
    fmt=fmt@entry=0x405ef850 "Statically linked executables are not supported yet!\n") at runtime.cc:145
#2  0x00000000402b96e7 in elf::object::process_headers (
    this=this@entry=0x600001c58600) at core/elf.cc:537
#3  0x00000000402c2fd0 in elf::program::load_object (
    this=this@entry=0x6000001e4020, name="/usr/lib/liblua53.so",
    extra_path=std::vector of length 0, capacity 0,
    loaded_objects=std::vector of length 3, capacity 4 = {...})
    at core/elf.cc:1479
#4  0x00000000402c1def in elf::object::load_needed (this=<optimized out>,
    loaded_objects=...) at /usr/include/c++/13/bits/basic_string.tcc:238
#5  0x00000000402c3053 in elf::program::load_object (
    this=this@entry=0x6000001e4020, name="/cli",
    extra_path=std::vector of length 0, capacity 0,
    loaded_objects=std::vector of length 3, capacity 4 = {...})
    at /usr/include/c++/13/bits/shared_ptr_base.h:1665

So the "error" is in /usr/lib/liblua53.so. This is a shared library - not an executable at all:

$ file **/liblua53.so
modules/lua/upstream/lua5.3/liblua53.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=eb52863fe00206f6b720a131074ae8de9f7aae52, not stripped


It is determined by this expression:

bool is_statically_linked() { return !_is_dynamically_linked_executable && _ehdr.e_entry; }

But what if it's not an executable at all, just a shared library?

--
You received this message because you are subscribed to the Google Groups "OSv Development" group.
To unsubscribe from this group and stop receiving emails from it, send an email to osv-dev+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/osv-dev/8eed9d97-8548-4041-bf48-def7334bcaa4n%40googlegroups.com.

Nadav Har'El

unread,
Sep 19, 2023, 7:57:43 AM9/19/23
to Waldek Kozaczuk, OSv Development
By the way, readelf -W -l modules/lua/upstream/lua5.3/liblua53.so gives:

Elf file type is DYN (Shared object file)
Entry point 0x8a90
There are 11 program headers, starting at offset 64

Program Headers:
...

So surprisingly, it DOES have an entry point set. Which is silly (if I try to run this "program" in Linux, it dumps core), but a fact.
I think that if the elf file is *dynamic* we should immediately rule out it being a "statically linked executable", even if it has an entry point.

Moreover, we should only care about the "entry point" if we ever try to "run" an object. In this case, the object is just being loaded as
a dependency - it is not run - so we shouldn't have cared that it has an entry point.


--
Nadav Har'El
n...@scylladb.com

Reply all
Reply to author
Forward
0 new messages