From 41623f74a9f38a15c3e8f1f70535db7a642c0abf Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Fri, 4 Oct 2024 11:00:32 -0600 Subject: [PATCH 1/3] Backport EFI_HTTP_ERROR status code The define can be dropped when gnu-efi is updated to include de6f9259e8476495c78babbc25250a59de7f3942. Signed-off-by: Dan Nicholson --- include/errors.h | 3 +++ lib/console.c | 1 + 2 files changed, 4 insertions(+) diff --git a/include/errors.h b/include/errors.h index 67d821e03..eab58453c 100644 --- a/include/errors.h +++ b/include/errors.h @@ -9,5 +9,8 @@ #ifndef EFI_SECURITY_VIOLATION #define EFI_SECURITY_VIOLATION EFIERR(26) #endif +#ifndef EFI_HTTP_ERROR +#define EFI_HTTP_ERROR EFIERR(35) +#endif #endif /* SHIM_ERRORS_H */ diff --git a/lib/console.c b/lib/console.c index a751f79df..f60383209 100644 --- a/lib/console.c +++ b/lib/console.c @@ -651,6 +651,7 @@ static struct { { EFI_PROTOCOL_ERROR, L"Protocol Error"}, { EFI_INCOMPATIBLE_VERSION, L"Incompatible Version"}, { EFI_SECURITY_VIOLATION, L"Security Violation"}, + { EFI_HTTP_ERROR, L"HTTP Error"}, // warnings { EFI_WARN_UNKNOWN_GLYPH, L"Warning Unknown Glyph"}, From 4fa0495e8e0e95ba7a1e29878d1e744644cd413f Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Wed, 2 Oct 2024 23:59:34 -0600 Subject: [PATCH 2/3] netboot: Convert TFTP error codes to EFI status codes This allows the caller to make a more informed decision about how to handle the error. The error code is available in the TftpError field of the Mode interface. In particular, by mapping the TFTP errors to EFI_INVALID_PARAMTER and EFI_NOT_FOUND, shim will try to fetch the default second stage image like it does when loading images from disk. Unfortunately, some firmware doesn't fill in the error fields, so a generic EFI_TFTP_ERROR to EFI_NOT_FOUND conversion is included. Signed-off-by: Dan Nicholson --- netboot.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/netboot.c b/netboot.c index d8b109355..a4cc7acde 100644 --- a/netboot.c +++ b/netboot.c @@ -16,6 +16,16 @@ #define ntohs(x) __builtin_bswap16(x) /* supported both by GCC and clang */ #define htons(x) ntohs(x) +/* TFTP error codes from RFC 1350 */ +#define TFTP_ERROR_NOT_DEFINED 0 /* Not defined, see error message (if any). */ +#define TFTP_ERROR_NOT_FOUND 1 /* File not found. */ +#define TFTP_ERROR_ACCESS 2 /* Access violation. */ +#define TFTP_ERROR_NO_SPACE 3 /* Disk full or allocation exceeded. */ +#define TFTP_ERROR_ILLEGAL_OP 4 /* Illegal TFTP operation. */ +#define TFTP_ERROR_UNKNOWN_ID 5 /* Unknown transfer ID. */ +#define TFTP_ERROR_EXISTS 6 /* File already exists. */ +#define TFTP_ERROR_NO_USER 7 /* No such user. */ + static EFI_PXE_BASE_CODE *pxe; static EFI_IP_ADDRESS tftp_addr; static CHAR8 *full_path; @@ -333,6 +343,31 @@ EFI_STATUS parseNetbootinfo(EFI_HANDLE image_handle UNUSED, CHAR8 *netbootname) return efi_status; } +/* Convert a TFTP error code to an EFI status code. */ +static EFI_STATUS +status_from_error(UINT8 error_code) +{ + switch (error_code) { + case TFTP_ERROR_NOT_FOUND: + return EFI_NOT_FOUND; + case TFTP_ERROR_ACCESS: + case TFTP_ERROR_NO_USER: + return EFI_ACCESS_DENIED; + case TFTP_ERROR_NO_SPACE: + return EFI_VOLUME_FULL; + case TFTP_ERROR_ILLEGAL_OP: + return EFI_PROTOCOL_ERROR; + case TFTP_ERROR_UNKNOWN_ID: + return EFI_INVALID_PARAMETER; + case TFTP_ERROR_EXISTS: + return EFI_WRITE_PROTECTED; + case TFTP_ERROR_NOT_DEFINED: + default: + /* Use a generic TFTP error for anything else. */ + return EFI_TFTP_ERROR; + } +} + EFI_STATUS FetchNetbootimage(EFI_HANDLE image_handle UNUSED, VOID **buffer, UINT64 *bufsiz) { EFI_STATUS efi_status; @@ -362,8 +397,28 @@ EFI_STATUS FetchNetbootimage(EFI_HANDLE image_handle UNUSED, VOID **buffer, UINT goto try_again; } - if (EFI_ERROR(efi_status) && *buffer) { - FreePool(*buffer); + if (EFI_ERROR(efi_status)) { + if (pxe->Mode->TftpErrorReceived) { + console_print(L"TFTP error %u: %a\n", + pxe->Mode->TftpError.ErrorCode, + pxe->Mode->TftpError.ErrorString); + + efi_status = status_from_error(pxe->Mode->TftpError.ErrorCode); + } else if (efi_status == EFI_TFTP_ERROR) { + /* + * Unfortunately, some firmware doesn't fill in the + * error details. Treat all TFTP errors like file not + * found so shim falls back to the default loader. + * + * https://github.com/tianocore/edk2/pull/6287 + */ + console_print(L"Unknown TFTP error, treating as file not found\n"); + efi_status = EFI_NOT_FOUND; + } + + if (*buffer) { + FreePool(*buffer); + } } return efi_status; } From bd3a507518825323e466d80e75f7176a54f6f0d7 Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Wed, 2 Oct 2024 18:05:43 -0600 Subject: [PATCH 3/3] httpboot: Convert HTTP status codes to EFI status codes This allows the caller to make a more informed decision about how to handle the error. In particular, by mapping HTTP errors to EFI_INVALID_PARAMETER and EFI_NOT_FOUND, shim will try to fetch the default second stage image like it does when loading images from disk. Note that this also changes the default to return EFI_HTTP_ERROR instead of EFI_ABORTED. This should not change any behavior as EFI_ABORTED wasn't being handled before, but letting the caller decide what to do with an unknown HTTP error is more appropriate. Signed-off-by: Dan Nicholson --- httpboot.c | 47 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/httpboot.c b/httpboot.c index ac9ea25c5..4b26fc8db 100644 --- a/httpboot.c +++ b/httpboot.c @@ -55,6 +55,51 @@ convert_http_status_code (EFI_HTTP_STATUS_CODE status_code) return 0; } +/* Convert an HTTP status code to an EFI status code. */ +static EFI_STATUS +efi_status_from_http_status(EFI_HTTP_STATUS_CODE status_code) +{ + switch (status_code) { + case HTTP_STATUS_400_BAD_REQUEST: + case HTTP_STATUS_411_LENGTH_REQUIRED: + case HTTP_STATUS_413_REQUEST_ENTITY_TOO_LARGE: + case HTTP_STATUS_414_REQUEST_URI_TOO_LARGE: + case HTTP_STATUS_415_UNSUPPORTED_MEDIA_TYPE: + case HTTP_STATUS_416_REQUESTED_RANGE_NOT_SATISFIED: + case HTTP_STATUS_417_EXPECTATION_FAILED: + return EFI_INVALID_PARAMETER; + case HTTP_STATUS_401_UNAUTHORIZED: + case HTTP_STATUS_402_PAYMENT_REQUIRED: + case HTTP_STATUS_403_FORBIDDEN: + case HTTP_STATUS_407_PROXY_AUTHENTICATION_REQUIRED: + return EFI_ACCESS_DENIED; + case HTTP_STATUS_404_NOT_FOUND: + case HTTP_STATUS_410_GONE: + return EFI_NOT_FOUND; + case HTTP_STATUS_405_METHOD_NOT_ALLOWED: + case HTTP_STATUS_501_NOT_IMPLEMENTED: + return EFI_UNSUPPORTED; + case HTTP_STATUS_406_NOT_ACCEPTABLE: + return EFI_NO_MEDIA; + case HTTP_STATUS_408_REQUEST_TIME_OUT: + case HTTP_STATUS_504_GATEWAY_TIME_OUT: + return EFI_TIMEOUT; + case HTTP_STATUS_409_CONFLICT: + case HTTP_STATUS_412_PRECONDITION_FAILED: + return EFI_MEDIA_CHANGED; + case HTTP_STATUS_500_INTERNAL_SERVER_ERROR: + case HTTP_STATUS_502_BAD_GATEWAY: + return EFI_DEVICE_ERROR; + case HTTP_STATUS_503_SERVICE_UNAVAILABLE: + return EFI_NOT_READY; + case HTTP_STATUS_505_HTTP_VERSION_NOT_SUPPORTED: + return EFI_INCOMPATIBLE_VERSION; + default: + /* Use a generic HTTP error for anything else. */ + return EFI_HTTP_ERROR; + } +} + static EFI_DEVICE_PATH *devpath; static EFI_MAC_ADDRESS mac_addr; static IPv4_DEVICE_PATH ip4_node; @@ -565,7 +610,7 @@ receive_http_response(EFI_HTTP_PROTOCOL *http, VOID **buffer, UINT64 *buf_size) if (http_status != HTTP_STATUS_200_OK) { perror(L"HTTP Status Code: %d\n", convert_http_status_code(http_status)); - efi_status = EFI_ABORTED; + efi_status = efi_status_from_http_status(http_status); goto error; }