efi_loader: fix free() before use in RX path

Patrick Wildt patrick at blueri.se
Tue Oct 6 16:56:31 CEST 2020


Hi,

I have an EFI application doing TFTP on an i.MX8MM board.  The EFI
application uses the Simple Network Protocol and does ARP itself.
ARP didn't work, and I saw that the replies that were received on
the board were broken.

Good, as seen from the sending host:
2acbc7b16422001999ed548808060001080006040002001999ed5488ac1f00012acbc7b16422ac1f007f000000000000000000000000000000000000

Bad, as seen on the i.MX8MM board:
60a7fc7f0000000060a7fc7f00000000080006040002001999ed5488ac1f00012acbc7b16422ac1f007f000000000000000000000000000000000000

As you can see, in the received packet, it looks like the the first
16 bytes were overwritten, and those look like two 64-bit pointer.

Looking through the stack, with uclass enabled, the code does:

eth_rx():
[..]
    ret = eth_get_ops(current)->recv(current, flags, &packet);
    flags = 0;
    if (ret > 0)
            net_process_received_packet(packet, ret);
    if (ret >= 0 && eth_get_ops(current)->free_pkt)
            eth_get_ops(current)->free_pkt(current, packet, ret);
[..]

recv returns the packet, free_pkt free()s the packet.  Thus we may
only use the packet's contents between the recv and the free_pkt.

net_process_received_packet():
[..]
    net_rx_packet = in_packet;
    net_rx_packet_len = len;
[..]
    if (push_packet) {
        (*push_packet)(in_packet, len);
        return;
   }
[..]

push_packet is set to efi_net_push during efi_network_timer_notify,
which does nothing more than to set a status flag:

static void efi_net_push(void *pkt, int len)
{
    new_rx_packet = true;
}

This does *not* touch the packet at all.  Instead, efi_net_receive
will, as soon as the EFI application calls it, access net_rx_packet
directly.  But this only happens *after* the packet has already been
free()d.  Something else could have already started using that memory!

The following diff changes efi_net_push() to allocate a new buffer, but
I think it's not enough since eth_rx() will try to receive 32 packets
at one time.  So I think maybe efi_net_push() needs to add the packets
to a list, and efi_net_receive() takes the first packet from that list.

Best regards,
Patrick

Signed-off-by: Patrick Wildt <patrick at blueri.se>

diff --git a/lib/efi_loader/efi_net.c b/lib/efi_loader/efi_net.c
index 22f0123eca..6e3c29cba2 100644
--- a/lib/efi_loader/efi_net.c
+++ b/lib/efi_loader/efi_net.c
@@ -25,6 +25,8 @@ static const efi_guid_t efi_pxe_base_code_protocol_guid =
 					EFI_PXE_BASE_CODE_PROTOCOL_GUID;
 static struct efi_pxe_packet *dhcp_ack;
 static bool new_rx_packet;
+static uchar *efi_net_rx_packet;
+static int efi_net_rx_packet_len;
 static void *new_tx_packet;
 static void *transmit_buffer;
 
@@ -607,11 +609,11 @@ static efi_status_t EFIAPI efi_net_receive
 		goto out;
 	}
 	/* Fill export parameters */
-	eth_hdr = (struct ethernet_hdr *)net_rx_packet;
+	eth_hdr = (struct ethernet_hdr *)efi_net_rx_packet;
 	protlen = ntohs(eth_hdr->et_protlen);
 	if (protlen == 0x8100) {
 		hdr_size += 4;
-		protlen = ntohs(*(u16 *)&net_rx_packet[hdr_size - 2]);
+		protlen = ntohs(*(u16 *)&efi_net_rx_packet[hdr_size - 2]);
 	}
 	if (header_size)
 		*header_size = hdr_size;
@@ -621,16 +623,19 @@ static efi_status_t EFIAPI efi_net_receive
 		memcpy(src_addr, eth_hdr->et_src, ARP_HLEN);
 	if (protocol)
 		*protocol = protlen;
-	if (*buffer_size < net_rx_packet_len) {
+	if (*buffer_size < efi_net_rx_packet_len) {
 		/* Packet doesn't fit, try again with bigger buffer */
-		*buffer_size = net_rx_packet_len;
+		*buffer_size = efi_net_rx_packet_len;
 		ret = EFI_BUFFER_TOO_SMALL;
 		goto out;
 	}
 	/* Copy packet */
-	memcpy(buffer, net_rx_packet, net_rx_packet_len);
-	*buffer_size = net_rx_packet_len;
+	memcpy(buffer, efi_net_rx_packet, efi_net_rx_packet_len);
+	*buffer_size = efi_net_rx_packet_len;
 	new_rx_packet = 0;
+	free(efi_net_rx_packet);
+	efi_net_rx_packet = NULL;
+	efi_net_rx_packet_len = 0;
 	this->int_status &= ~EFI_SIMPLE_NETWORK_RECEIVE_INTERRUPT;
 out:
 	return EFI_EXIT(ret);
@@ -664,6 +669,13 @@ void efi_net_set_dhcp_ack(void *pkt, int len)
  */
 static void efi_net_push(void *pkt, int len)
 {
+	efi_net_rx_packet = malloc(len);
+	if (efi_net_rx_packet == NULL)
+		return;
+
+	memcpy(efi_net_rx_packet, pkt, len);
+	efi_net_rx_packet_len = len;
+
 	new_rx_packet = true;
 }
 


More information about the U-Boot mailing list