diff --git a/mcpp.toml b/mcpp.toml index 95f036c..e3c8db6 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "openkal-linux" -version = "0.11.0" +version = "0.12.0" description = "The reference implementation of openkal for Linux, written on the kernel's own system-call interface so that it can be placed beneath a C library as well as above one." license = "Apache-2.0" diff --git a/src/process.cpp b/src/process.cpp index 66a70ae..33434e8 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -279,8 +279,39 @@ int kal_process_spawn(const kal_spawn* how, okl::sys(okl::nr_exit_group, 127); } + // ⚠️⚠️ THE BASE IS DUPLICATED SO THAT IT SURVIVES THE REPLACEMENT, AND + // WITHOUT THIS A WHOLE CLASS OF PROGRAMS COULD NOT BE STARTED AT ALL. + // + // `execveat' with a dirfd and a relative name gives the program's name to + // the kernel as `/dev/fd//'. That spelling is invisible to a + // caller and harmless for an ordinary executable --- the kernel already + // holds the file open. It stops being harmless the moment the program + // needs an INTERPRETER: a `#!' script, or a binary of another + // architecture registered through `binfmt_misc'. The kernel then starts + // the interpreter and hands it that name to open --- AFTER the + // replacement, by which time a close-on-exec dirfd is gone. The + // interpreter is told the script does not exist. + // + // ⭐ Measured in twenty lines of plain C, with everything else identical: + // + // dirfd WITH O_CLOEXEC execveat -> ENOENT + // dirfd WITHOUT O_CLOEXEC STARTED ok + // + // ⚠️ It is not a property of one architecture. It was FOUND on aarch64, + // where every foreign binary needs the binfmt interpreter and so every + // start failed --- and it was mistaken there for a limit of the emulator. + // It reproduces natively on x86_64 with a `#!' script, which is what a + // consumer meets on any machine. + // + // ⚠️ Duplicated HERE, in the started image, and not where the preopens are + // made: the caller's own descriptors stay close-on-exec, which is what + // every other operation of this implementation relies upon. `dup' clears + // the flag by definition, so the copy is the exec-visible one. + const okl_long visible = okl::sys(okl::nr_fcntl, b, okl::f_dupfd, 0); + const okl_long base = okl::failed(visible) ? b : visible; + const okl_long why = - okl::sys(okl::nr_execveat, b, reinterpret_cast(p.buf), + okl::sys(okl::nr_execveat, base, reinterpret_cast(p.buf), reinterpret_cast(args.slots), reinterpret_cast(envs.slots), 0); // Reached only when the replacement did not happen, because when it does diff --git a/src/sys.h b/src/sys.h index 14a3ec5..82c4078 100644 --- a/src/sys.h +++ b/src/sys.h @@ -258,6 +258,13 @@ enum : okl_long { // closes what is on it. f_dupfd_cloexec = 1030, + // ⭐ The same primitive WITHOUT the flag, which is the point of having both. + // A descriptor duplicated this way survives a replacement, and starting a + // program that needs an interpreter depends on exactly that --- see the + // duplication in `kal_process_spawn'. `dup' would do as well and this + // architecture pair does not agree on whether it exists. + f_dupfd = 0, + // ⭐⭐ THE OPEN-FILE FORM AND NOT THE PROCESS FORM, WHICH IS THE WHOLE // DIFFERENCE. //