]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[efi] Attempt to fetch autoexec script via TFTP
authorMichael Brown <mcb30@ipxe.org>
Mon, 17 Jan 2022 16:17:17 +0000 (16:17 +0000)
committerMichael Brown <mcb30@ipxe.org>
Tue, 18 Jan 2022 13:16:12 +0000 (13:16 +0000)
Attempt to fetch the autoexec.ipxe script via TFTP using the PXE base
code protocol installed on the loaded image's device handle, if
present.

This provides a generic alternative to the use of an embedded script
for chainloaded binaries, which is particularly useful in a UEFI
Secure Boot environment since it allows the script to be modified
without the need to sign a new binary.

As a side effect, this also provides a third method for breaking the
PXE chainloading loop (as an alternative to requiring an embedded
script or custom DHCP server configuration).

Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/interface/efi/efi_autoexec.c

index 881c30c7e71dbdb8dc1cf5c9c7d7a734d34d5249..79d4a4cafca4cadc353a5c4a3d3f8ca2d701a9ca 100644 (file)
 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
 
 #include <string.h>
+#include <stdlib.h>
 #include <errno.h>
 #include <ipxe/image.h>
 #include <ipxe/init.h>
+#include <ipxe/in.h>
 #include <ipxe/efi/efi.h>
 #include <ipxe/efi/efi_autoexec.h>
+#include <ipxe/efi/Protocol/PxeBaseCode.h>
 #include <ipxe/efi/Protocol/SimpleFileSystem.h>
 #include <ipxe/efi/Guid/FileInfo.h>
 
@@ -169,6 +172,175 @@ static int efi_autoexec_filesystem ( EFI_HANDLE device ) {
        return rc;
 }
 
+/**
+ * Load autoexec script from TFTP server
+ *
+ * @v device           Device handle
+ * @ret rc             Return status code
+ */
+static int efi_autoexec_tftp ( EFI_HANDLE device ) {
+       EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+       union {
+               void *interface;
+               EFI_PXE_BASE_CODE_PROTOCOL *pxe;
+       } u;
+       EFI_PXE_BASE_CODE_MODE *mode;
+       EFI_PXE_BASE_CODE_PACKET *packet;
+       union {
+               struct in_addr in;
+               EFI_IP_ADDRESS ip;
+       } server;
+       size_t filename_max;
+       char *filename;
+       char *sep;
+       UINT64 size;
+       VOID *data;
+       EFI_STATUS efirc;
+       int rc;
+
+       /* Open PXE base code protocol */
+       if ( ( efirc = bs->OpenProtocol ( device,
+                                         &efi_pxe_base_code_protocol_guid,
+                                         &u.interface, efi_image_handle,
+                                         device,
+                                         EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){
+               rc = -EEFI ( efirc );
+               DBGC ( device, "EFI %s has no PXE base code instance: %s\n",
+                      efi_handle_name ( device ), strerror ( rc ) );
+               goto err_pxe;
+       }
+
+       /* Do not attempt to parse DHCPv6 packets */
+       mode = u.pxe->Mode;
+       if ( mode->UsingIpv6 ) {
+               rc = -ENOTSUP;
+               DBGC ( device, "EFI %s has IPv6 PXE base code\n",
+                      efi_handle_name ( device ) );
+               goto err_ipv6;
+       }
+
+       /* Identify relevant reply packet */
+       if ( mode->PxeReplyReceived &&
+            mode->PxeReply.Dhcpv4.BootpBootFile[0] ) {
+               /* Use boot filename if present in PXE reply */
+               DBGC ( device, "EFI %s using PXE reply filename\n",
+                      efi_handle_name ( device ) );
+               packet = &mode->PxeReply;
+       } else if ( mode->DhcpAckReceived &&
+                   mode->DhcpAck.Dhcpv4.BootpBootFile[0] ) {
+               /* Otherwise, use boot filename if present in DHCPACK */
+               DBGC ( device, "EFI %s using DHCPACK filename\n",
+                      efi_handle_name ( device ) );
+               packet = &mode->DhcpAck;
+       } else if ( mode->ProxyOfferReceived &&
+                   mode->ProxyOffer.Dhcpv4.BootpBootFile[0] ) {
+               /* Otherwise, use boot filename if present in ProxyDHCPOFFER */
+               DBGC ( device, "EFI %s using ProxyDHCPOFFER filename\n",
+                      efi_handle_name ( device ) );
+               packet = &mode->ProxyOffer;
+       } else {
+               /* No boot filename available */
+               rc = -ENOENT;
+               DBGC ( device, "EFI %s has no PXE boot filename\n",
+                      efi_handle_name ( device ) );
+               goto err_packet;
+       }
+
+       /* Allocate filename */
+       filename_max = ( sizeof ( packet->Dhcpv4.BootpBootFile )
+                        + ( sizeof ( efi_autoexec_name ) - 1 /* NUL */ )
+                        + 1 /* NUL */ );
+       filename = zalloc ( filename_max );
+       if ( ! filename ) {
+               rc = -ENOMEM;
+               goto err_filename;
+       }
+
+       /* Extract next-server address and boot filename */
+       memset ( &server, 0, sizeof ( server ) );
+       memcpy ( &server.in, packet->Dhcpv4.BootpSiAddr,
+                sizeof ( server.in ) );
+       memcpy ( filename, packet->Dhcpv4.BootpBootFile,
+                sizeof ( packet->Dhcpv4.BootpBootFile ) );
+
+       /* Update filename to autoexec script name */
+       sep = strrchr ( filename, '/' );
+       if ( ! sep )
+               sep = strrchr ( filename, '\\' );
+       if ( ! sep )
+               sep = ( filename - 1 );
+       strcpy ( ( sep + 1 ), efi_autoexec_name );
+
+       /* Get file size */
+       if ( ( efirc = u.pxe->Mtftp ( u.pxe,
+                                     EFI_PXE_BASE_CODE_TFTP_GET_FILE_SIZE,
+                                     NULL, FALSE, &size, NULL, &server.ip,
+                                     ( ( UINT8 * ) filename ), NULL,
+                                     FALSE ) ) != 0 ) {
+               rc = -EEFI ( efirc );
+               DBGC ( device, "EFI %s could not get size of %s:%s: %s\n",
+                      efi_handle_name ( device ), inet_ntoa ( server.in ),
+                      filename, strerror ( rc ) );
+               goto err_size;
+       }
+
+       /* Ignore zero-length files */
+       if ( ! size ) {
+               rc = -EINVAL;
+               DBGC ( device, "EFI %s has zero-length %s:%s\n",
+                      efi_handle_name ( device ), inet_ntoa ( server.in ),
+                      filename );
+               goto err_empty;
+       }
+
+       /* Allocate temporary copy */
+       if ( ( efirc = bs->AllocatePool ( EfiBootServicesData, size,
+                                         &data ) ) != 0 ) {
+               rc = -EEFI ( efirc );
+               DBGC ( device, "EFI %s could not allocate %s:%s: %s\n",
+                      efi_handle_name ( device ), inet_ntoa ( server.in ),
+                      filename, strerror ( rc ) );
+               goto err_alloc;
+       }
+
+       /* Download file */
+       if ( ( efirc = u.pxe->Mtftp ( u.pxe, EFI_PXE_BASE_CODE_TFTP_READ_FILE,
+                                     data, FALSE, &size, NULL, &server.ip,
+                                     ( ( UINT8 * ) filename ), NULL,
+                                     FALSE ) ) != 0 ) {
+               rc = -EEFI ( efirc );
+               DBGC ( device, "EFI %s could not download %s:%s: %s\n",
+                      efi_handle_name ( device ), inet_ntoa ( server.in ),
+                      filename, strerror ( rc ) );
+               goto err_download;
+       }
+
+       /* Record autoexec script */
+       efi_autoexec = data;
+       efi_autoexec_len = size;
+       data = NULL;
+       DBGC ( device, "EFI %s found %s:%s\n", efi_handle_name ( device ),
+              inet_ntoa ( server.in ), filename );
+
+       /* Success */
+       rc = 0;
+
+ err_download:
+       if ( data )
+               bs->FreePool ( data );
+ err_alloc:
+ err_empty:
+ err_size:
+       free ( filename );
+ err_filename:
+ err_packet:
+ err_ipv6:
+       bs->CloseProtocol ( device, &efi_pxe_base_code_protocol_guid,
+                           efi_image_handle, device );
+ err_pxe:
+       return rc;
+}
+
 /**
  * Load autoexec script
  *
@@ -186,6 +358,10 @@ int efi_autoexec_load ( EFI_HANDLE device ) {
        if ( ( rc = efi_autoexec_filesystem ( device ) ) == 0 )
                return 0;
 
+       /* Try loading via TFTP, if supported */
+       if ( ( rc = efi_autoexec_tftp ( device ) ) == 0 )
+               return 0;
+
        return -ENOENT;
 }