From 2dafc11dfc9d96cb40f270753cfefb8b983a7a72 Mon Sep 17 00:00:00 2001 From: Hanlu Li Date: Tue, 4 Aug 2026 14:44:45 +0800 Subject: [PATCH] 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 --- .github/workflows/tests.yml | 1 + linux-user/elfload.c | 81 ++++++++- target/i386/latx/include/latx-runtime.h | 1 + target/i386/latx/latx-runtime.c | 12 ++ tests/latx/latx-config-regression.c | 5 + tests/runtime/test-latx-runtime-info.sh | 213 +++++++++++++++++++++++- 6 files changed, 311 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d0f4bf639a..4e1c745f96 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -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 \ diff --git a/linux-user/elfload.c b/linux-user/elfload.c index 82700a31f1..d617db5271 100644 --- a/linux-user/elfload.c +++ b/linux-user/elfload.c @@ -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); diff --git a/target/i386/latx/include/latx-runtime.h b/target/i386/latx/include/latx-runtime.h index 6d537156b6..09c53439b4 100644 --- a/target/i386/latx/include/latx-runtime.h +++ b/target/i386/latx/include/latx-runtime.h @@ -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 diff --git a/target/i386/latx/latx-runtime.c b/target/i386/latx/latx-runtime.c index 872d10d0f8..6f5029f008 100644 --- a/target/i386/latx/latx-runtime.c +++ b/target/i386/latx/latx-runtime.c @@ -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"; + } +} diff --git a/tests/latx/latx-config-regression.c b/tests/latx/latx-config-regression.c index 9cd532f527..2bbc14b2a2 100644 --- a/tests/latx/latx-config-regression.c +++ b/tests/latx/latx-config-regression.c @@ -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) diff --git a/tests/runtime/test-latx-runtime-info.sh b/tests/runtime/test-latx-runtime-info.sh index d6b20dab3b..c8523c12e2 100755 --- a/tests/runtime/test-latx-runtime-info.sh +++ b/tests/runtime/test-latx-runtime-info.sh @@ -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( + " "$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"