LATX: guide users when guest runtimes fail to load
A PT_INTERP failure previously exposed only a low-level host error, leaving users unable to tell which guest ABI, runtime root, or loader path LATX had selected. Report the selected runtime source, the guest PT_INTERP value, the configured candidate from the shared path resolver, the actually attempted host path, a stable failure category, and the underlying detail. Use QEMU error-reporting APIs and direct users to the companion latu-runtime-manager status command. The diagnostic is emitted only at the interpreter load failure. Successful dynamic guests and static guests retain their existing behavior. Synthetic x86_64 and i386 fixtures cover absolute and relative interpreters, missing, unreadable, corrupt, truncated, valid dynamic, and static cases. Signed-off-by: Hanlu Li <heuleehanlu@gmail.com>
This commit is contained in:
parent
0863f52ad6
commit
2dafc11dfc
|
|
@ -105,6 +105,7 @@ jobs:
|
|||
--disable-docs \
|
||||
--with-git-submodules=ignore
|
||||
cd ..
|
||||
export LATX_RUNTIME_TEST_SKIP_GUEST_EXECUTION=1
|
||||
ASAN_OPTIONS=detect_leaks=0 \
|
||||
UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1 \
|
||||
python3 -B meson/meson.py test \
|
||||
|
|
|
|||
|
|
@ -2970,25 +2970,85 @@ static bool load_elf_image(const char *image_name, const ImageSource *src,
|
|||
exit(-1);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_LATX
|
||||
static void latx_report_runtime_loader_failure(const char *filename,
|
||||
const char *configured_path,
|
||||
const char *attempted_path,
|
||||
const char *reason,
|
||||
const char *detail)
|
||||
{
|
||||
g_autofree char *escaped_abi = NULL;
|
||||
g_autofree char *escaped_interp = NULL;
|
||||
g_autofree char *escaped_root = NULL;
|
||||
g_autofree char *escaped_configured = NULL;
|
||||
g_autofree char *escaped_attempted = NULL;
|
||||
g_autofree char *escaped_detail = NULL;
|
||||
|
||||
escaped_abi = g_strescape(latx_runtime_guest_abi(), NULL);
|
||||
escaped_interp = g_strescape(filename, NULL);
|
||||
escaped_root = g_strescape(interp_prefix, NULL);
|
||||
escaped_configured = g_strescape(configured_path, NULL);
|
||||
escaped_attempted = g_strescape(attempted_path, NULL);
|
||||
escaped_detail = g_strescape(detail, NULL);
|
||||
|
||||
error_report("LATU: guest runtime loader failure");
|
||||
error_printf(" guest ABI: %s\n"
|
||||
" PT_INTERP: %s\n"
|
||||
" runtime root: %s\n"
|
||||
" runtime source: %s\n"
|
||||
" configured loader: %s\n"
|
||||
" attempted loader: %s\n"
|
||||
" failure reason: %s\n"
|
||||
" detail: %s\n"
|
||||
" next step: run 'latu-runtime-manager status'\n",
|
||||
escaped_abi, escaped_interp, escaped_root,
|
||||
latx_runtime_prefix_source_name(), escaped_configured,
|
||||
escaped_attempted, reason, escaped_detail);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void load_elf_interp(const char *filename, struct image_info *info,
|
||||
char bprm_buf[BPRM_BUF_SIZE])
|
||||
{
|
||||
struct elfhdr ehdr;
|
||||
ImageSource src;
|
||||
const char *loader_path = path(filename);
|
||||
int fd, retval;
|
||||
Error *err = NULL;
|
||||
#ifdef CONFIG_LATX
|
||||
g_autofree char *configured_path = path_get_prefixed(filename);
|
||||
bool header_valid = false;
|
||||
#endif
|
||||
|
||||
fd = open(path(filename), O_RDONLY);
|
||||
fd = open(loader_path, O_RDONLY);
|
||||
if (fd < 0) {
|
||||
#ifdef CONFIG_LATX
|
||||
int saved_errno = errno;
|
||||
|
||||
latx_report_runtime_loader_failure(
|
||||
filename, configured_path, loader_path,
|
||||
latx_runtime_loader_errno_name(saved_errno),
|
||||
strerror(saved_errno));
|
||||
#else
|
||||
error_setg_file_open(&err, errno, filename);
|
||||
error_report_err(err);
|
||||
#endif
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
retval = read(fd, bprm_buf, BPRM_BUF_SIZE);
|
||||
if (retval < 0) {
|
||||
#ifdef CONFIG_LATX
|
||||
int saved_errno = errno;
|
||||
|
||||
latx_report_runtime_loader_failure(
|
||||
filename, configured_path, loader_path,
|
||||
latx_runtime_loader_errno_name(saved_errno),
|
||||
strerror(saved_errno));
|
||||
#else
|
||||
error_setg_errno(&err, errno, "Error reading file header");
|
||||
error_reportf_err(err, "%s: ", filename);
|
||||
#endif
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
|
|
@ -2996,8 +3056,27 @@ static void load_elf_interp(const char *filename, struct image_info *info,
|
|||
src.cache = bprm_buf;
|
||||
src.cache_size = retval;
|
||||
|
||||
#ifdef CONFIG_LATX
|
||||
if ((size_t)retval >= sizeof(ehdr)) {
|
||||
memcpy(&ehdr, bprm_buf, sizeof(ehdr));
|
||||
header_valid = elf_check_ident(&ehdr);
|
||||
if (header_valid) {
|
||||
bswap_ehdr(&ehdr);
|
||||
header_valid = elf_check_ehdr(&ehdr);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!load_elf_image(filename, &src, info, &ehdr, NULL, &err)) {
|
||||
#ifdef CONFIG_LATX
|
||||
latx_report_runtime_loader_failure(
|
||||
filename, configured_path, loader_path,
|
||||
header_valid ? "load_error" : "invalid_elf",
|
||||
error_get_pretty(err));
|
||||
error_free(err);
|
||||
#else
|
||||
error_reportf_err(err, "%s: ", filename);
|
||||
#endif
|
||||
exit(-1);
|
||||
}
|
||||
close(fd);
|
||||
|
|
|
|||
|
|
@ -20,5 +20,6 @@ void latx_runtime_option_source_set(LatxRuntimeSource source);
|
|||
void latx_runtime_prefix_selected(void);
|
||||
const char *latx_runtime_prefix_source_name(void);
|
||||
const char *latx_runtime_guest_abi(void);
|
||||
const char *latx_runtime_loader_errno_name(int errnum);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -52,3 +52,15 @@ const char *latx_runtime_guest_abi(void)
|
|||
return "i386";
|
||||
#endif
|
||||
}
|
||||
|
||||
const char *latx_runtime_loader_errno_name(int errnum)
|
||||
{
|
||||
switch (errnum) {
|
||||
case ENOENT:
|
||||
return "not_found";
|
||||
case EACCES:
|
||||
return "permission_denied";
|
||||
default:
|
||||
return "io_error";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,6 +66,11 @@ static void test_runtime_prefix_source(void)
|
|||
#else
|
||||
g_assert_cmpstr(latx_runtime_guest_abi(), ==, "i386");
|
||||
#endif
|
||||
|
||||
g_assert_cmpstr(latx_runtime_loader_errno_name(ENOENT), ==, "not_found");
|
||||
g_assert_cmpstr(latx_runtime_loader_errno_name(EACCES), ==,
|
||||
"permission_denied");
|
||||
g_assert_cmpstr(latx_runtime_loader_errno_name(EIO), ==, "io_error");
|
||||
}
|
||||
|
||||
static void test_user_config_path(void)
|
||||
|
|
|
|||
|
|
@ -41,6 +41,82 @@ if actual != expected:
|
|||
fail "unexpected $expected_source runtime information"
|
||||
}
|
||||
|
||||
assert_stderr()
|
||||
{
|
||||
expected=$1
|
||||
stderr_file=$2
|
||||
context=$3
|
||||
|
||||
grep -F -- "$expected" "$stderr_file" > /dev/null ||
|
||||
fail "$context: missing '$expected'"
|
||||
}
|
||||
|
||||
make_elf()
|
||||
{
|
||||
elf_kind=$1
|
||||
elf_path=$2
|
||||
elf_interp=$3
|
||||
elf_base=$4
|
||||
|
||||
"$python" - "$guest_abi" "$elf_kind" "$elf_path" \
|
||||
"$elf_interp" "$elf_base" <<'PY'
|
||||
import struct
|
||||
import sys
|
||||
|
||||
abi, kind, output, interpreter, base_arg = sys.argv[1:]
|
||||
base = int(base_arg, 0)
|
||||
elf_type = 3 if kind == "loader" else 2
|
||||
|
||||
if abi == "x86_64":
|
||||
elf_class = 2
|
||||
machine = 62
|
||||
ehsize = 64
|
||||
phentsize = 56
|
||||
code = bytes.fromhex("b83c00000031ff0f05")
|
||||
pack_ehdr = lambda ident, entry, phnum: struct.pack(
|
||||
"<16sHHIQQQIHHHHHH", ident, elf_type, machine, 1, entry, ehsize, 0,
|
||||
0, ehsize, phentsize, phnum, 0, 0, 0)
|
||||
pack_load = lambda size: struct.pack(
|
||||
"<IIQQQQQQ", 1, 5, 0, base, base, size, size, 0x1000)
|
||||
pack_interp = lambda offset, size: struct.pack(
|
||||
"<IIQQQQQQ", 3, 4, offset, 0, 0, size, size, 1)
|
||||
elif abi == "i386":
|
||||
elf_class = 1
|
||||
machine = 3
|
||||
ehsize = 52
|
||||
phentsize = 32
|
||||
code = bytes.fromhex("b80100000031dbcd80")
|
||||
pack_ehdr = lambda ident, entry, phnum: struct.pack(
|
||||
"<16sHHIIIIIHHHHHH", ident, elf_type, machine, 1, entry, ehsize, 0,
|
||||
0, ehsize, phentsize, phnum, 0, 0, 0)
|
||||
pack_load = lambda size: struct.pack(
|
||||
"<IIIIIIII", 1, 0, base, base, size, size, 5, 0x1000)
|
||||
pack_interp = lambda offset, size: struct.pack(
|
||||
"<IIIIIIII", 3, offset, 0, 0, size, size, 4, 1)
|
||||
else:
|
||||
raise SystemExit(f"unsupported guest ABI: {abi}")
|
||||
|
||||
ident = b"\x7fELF" + bytes([elf_class, 1, 1, 0]) + bytes(8)
|
||||
interp_data = interpreter.encode() + b"\0" if kind == "dynamic" else b""
|
||||
phnum = 2 if interp_data else 1
|
||||
interp_offset = ehsize + phentsize * phnum
|
||||
code_offset = interp_offset + len(interp_data)
|
||||
total_size = code_offset + len(code)
|
||||
|
||||
ehdr = pack_ehdr(ident, base + code_offset, phnum)
|
||||
phdrs = pack_load(total_size)
|
||||
if interp_data:
|
||||
phdrs += pack_interp(interp_offset, len(interp_data))
|
||||
|
||||
with open(output, "wb") as f:
|
||||
if kind == "truncated":
|
||||
f.write(ehdr)
|
||||
else:
|
||||
f.write(ehdr + phdrs + interp_data + code)
|
||||
PY
|
||||
chmod +x "$elf_path"
|
||||
}
|
||||
|
||||
case "$guest_abi" in
|
||||
x86_64) config_name=latx-x86_64.conf ;;
|
||||
i386) config_name=latx-i386.conf ;;
|
||||
|
|
@ -82,4 +158,139 @@ output=$(HOME=$test_home LAT_VERSION=1 LATX_AVX_CPUID=1 \
|
|||
fail 'runtime info with a diagnostic option failed'
|
||||
assert_info "$workdir/global-root" user_config "$output"
|
||||
|
||||
echo "PASS: $guest_abi runtime selection information"
|
||||
case "$guest_abi" in
|
||||
x86_64)
|
||||
main_base=0x400000
|
||||
;;
|
||||
i386)
|
||||
main_base=0x08048000
|
||||
;;
|
||||
esac
|
||||
|
||||
empty_home=$workdir/empty-home
|
||||
mkdir -p "$empty_home"
|
||||
guest_loader=/latu-test/actual-$guest_abi-loader.so
|
||||
dynamic_guest=$workdir/dynamic-$guest_abi
|
||||
static_guest=$workdir/static-$guest_abi
|
||||
make_elf dynamic "$dynamic_guest" "$guest_loader" "$main_base"
|
||||
make_elf static "$static_guest" '' "$main_base"
|
||||
|
||||
missing_root=$workdir/missing-root
|
||||
mkdir -p "$missing_root"
|
||||
set +e
|
||||
HOME=$empty_home "$translator" -L "$missing_root" "$dynamic_guest" \
|
||||
> "$workdir/missing.stdout" 2> "$workdir/missing.stderr"
|
||||
missing_status=$?
|
||||
set -e
|
||||
[ "$missing_status" -ne 0 ] || fail 'missing loader unexpectedly succeeded'
|
||||
assert_stderr 'LATU: guest runtime loader failure' \
|
||||
"$workdir/missing.stderr" 'missing loader'
|
||||
assert_stderr "guest ABI: $guest_abi" \
|
||||
"$workdir/missing.stderr" 'missing loader'
|
||||
assert_stderr "PT_INTERP: $guest_loader" \
|
||||
"$workdir/missing.stderr" 'missing loader'
|
||||
assert_stderr "runtime root: $missing_root" \
|
||||
"$workdir/missing.stderr" 'missing loader'
|
||||
assert_stderr 'runtime source: command_line' \
|
||||
"$workdir/missing.stderr" 'missing loader'
|
||||
assert_stderr "configured loader: $missing_root$guest_loader" \
|
||||
"$workdir/missing.stderr" 'missing loader'
|
||||
assert_stderr "attempted loader: $guest_loader" \
|
||||
"$workdir/missing.stderr" 'missing loader'
|
||||
assert_stderr 'failure reason: not_found' \
|
||||
"$workdir/missing.stderr" 'missing loader'
|
||||
assert_stderr "next step: run 'latu-runtime-manager status'" \
|
||||
"$workdir/missing.stderr" 'missing loader'
|
||||
|
||||
relative_loader=latu-test/missing-$guest_abi-loader.so
|
||||
relative_guest=$workdir/relative-$guest_abi
|
||||
make_elf dynamic "$relative_guest" "$relative_loader" "$main_base"
|
||||
set +e
|
||||
(
|
||||
cd "$workdir"
|
||||
HOME=$empty_home "$translator" -L "$missing_root" "$relative_guest"
|
||||
) > "$workdir/relative.stdout" 2> "$workdir/relative.stderr"
|
||||
relative_status=$?
|
||||
set -e
|
||||
[ "$relative_status" -ne 0 ] || fail 'relative loader unexpectedly succeeded'
|
||||
assert_stderr "PT_INTERP: $relative_loader" \
|
||||
"$workdir/relative.stderr" 'relative loader'
|
||||
assert_stderr "configured loader: $relative_loader" \
|
||||
"$workdir/relative.stderr" 'relative loader'
|
||||
assert_stderr "attempted loader: $relative_loader" \
|
||||
"$workdir/relative.stderr" 'relative loader'
|
||||
assert_stderr 'failure reason: not_found' \
|
||||
"$workdir/relative.stderr" 'relative loader'
|
||||
|
||||
corrupt_root=$workdir/corrupt-root
|
||||
mkdir -p "$corrupt_root$(dirname "$guest_loader")"
|
||||
printf 'not an ELF loader\n' > "$corrupt_root$guest_loader"
|
||||
set +e
|
||||
HOME=$empty_home "$translator" -L "$corrupt_root" "$dynamic_guest" \
|
||||
> "$workdir/corrupt.stdout" 2> "$workdir/corrupt.stderr"
|
||||
corrupt_status=$?
|
||||
set -e
|
||||
[ "$corrupt_status" -ne 0 ] || fail 'corrupt loader unexpectedly succeeded'
|
||||
assert_stderr "PT_INTERP: $guest_loader" \
|
||||
"$workdir/corrupt.stderr" 'corrupt loader'
|
||||
assert_stderr "configured loader: $corrupt_root$guest_loader" \
|
||||
"$workdir/corrupt.stderr" 'corrupt loader'
|
||||
assert_stderr "attempted loader: $corrupt_root$guest_loader" \
|
||||
"$workdir/corrupt.stderr" 'corrupt loader'
|
||||
assert_stderr 'failure reason: invalid_elf' \
|
||||
"$workdir/corrupt.stderr" 'corrupt loader'
|
||||
|
||||
load_error_root=$workdir/load-error-root
|
||||
mkdir -p "$load_error_root$(dirname "$guest_loader")"
|
||||
make_elf truncated "$load_error_root$guest_loader" '' 0
|
||||
set +e
|
||||
HOME=$empty_home "$translator" -L "$load_error_root" "$dynamic_guest" \
|
||||
> "$workdir/load-error.stdout" 2> "$workdir/load-error.stderr"
|
||||
load_error_status=$?
|
||||
set -e
|
||||
[ "$load_error_status" -ne 0 ] || fail 'truncated loader unexpectedly succeeded'
|
||||
assert_stderr "attempted loader: $load_error_root$guest_loader" \
|
||||
"$workdir/load-error.stderr" 'truncated loader'
|
||||
assert_stderr 'failure reason: load_error' \
|
||||
"$workdir/load-error.stderr" 'truncated loader'
|
||||
|
||||
permission_root=$workdir/permission-root
|
||||
mkdir -p "$permission_root$(dirname "$guest_loader")"
|
||||
: > "$permission_root$guest_loader"
|
||||
chmod 000 "$permission_root$guest_loader"
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
set +e
|
||||
HOME=$empty_home "$translator" -L "$permission_root" "$dynamic_guest" \
|
||||
> "$workdir/permission.stdout" 2> "$workdir/permission.stderr"
|
||||
permission_status=$?
|
||||
set -e
|
||||
[ "$permission_status" -ne 0 ] ||
|
||||
fail 'unreadable loader unexpectedly succeeded'
|
||||
assert_stderr 'failure reason: permission_denied' \
|
||||
"$workdir/permission.stderr" 'unreadable loader'
|
||||
fi
|
||||
|
||||
if [ "${LATX_RUNTIME_TEST_SKIP_GUEST_EXECUTION:-0}" = 1 ]; then
|
||||
echo "SKIP: $guest_abi translated guest execution"
|
||||
else
|
||||
success_root=$workdir/success-root
|
||||
mkdir -p "$success_root$(dirname "$guest_loader")"
|
||||
make_elf loader "$success_root$guest_loader" '' 0
|
||||
HOME=$empty_home "$translator" -L "$success_root" "$dynamic_guest" \
|
||||
> "$workdir/success.stdout" 2> "$workdir/success.stderr" ||
|
||||
fail 'valid runtime loader failed'
|
||||
if grep -F 'LATU: guest runtime loader failure' \
|
||||
"$workdir/success.stderr" > /dev/null; then
|
||||
fail 'valid runtime loader emitted failure guidance'
|
||||
fi
|
||||
|
||||
HOME=$empty_home "$translator" -L "$missing_root" "$static_guest" \
|
||||
> "$workdir/static.stdout" 2> "$workdir/static.stderr" ||
|
||||
fail 'static guest failed without a runtime'
|
||||
if grep -F 'LATU: guest runtime loader failure' \
|
||||
"$workdir/static.stderr" > /dev/null; then
|
||||
fail 'static guest emitted runtime failure guidance'
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "PASS: $guest_abi runtime selection and loader guidance"
|
||||
|
|
|
|||
Loading…
Reference in New Issue