From 4adbb12d15af04f8f279a6290cd0195e57cc9e69 Mon Sep 17 00:00:00 2001 From: Daniel Axtens Date: Mon, 20 Sep 2021 01:12:24 +1000 Subject: [PATCH 21/32] net/tftp: Prevent a UAF and double-free from a failed seek A malicious tftp server can cause UAFs and a double free. An attempt to read from a network file is handled by grub_net_fs_read(). If the read is at an offset other than the current offset, grub_net_seek_real() is invoked. In grub_net_seek_real(), if a backwards seek cannot be satisfied from the currently received packets, and the underlying transport does not provide a seek method, then grub_net_seek_real() will close and reopen the network protocol layer. For tftp, the ->close() call goes to tftp_close() and frees the tftp_data_t file->data. The file->data pointer is not nulled out after the free. If the ->open() call fails, the file->data will not be reallocated and will continue point to a freed memory block. This could happen from a server refusing to send the requisite ack to the new tftp request, for example. The seek and the read will then fail, but the grub_file continues to exist: the failed seek does not necessarily cause the entire file to be thrown away (e.g. where the file is checked to see if it is gzipped/lzio/xz/etc., a read failure is interpreted as a decompressor passing on the file, not as an invalidation of the entire grub_file_t structure). This means subsequent attempts to read or seek the file will use the old file->data after free. Eventually, the file will be close()d again and file->data will be freed again. Mark a net_fs file that doesn't reopen as broken. Do not permit read() or close() on a broken file (seek is not exposed directly to the file API - it is only called as part of read, so this blocks seeks as well). As an additional defence, null out the ->data pointer if tftp_open() fails. That would have lead to a simple null pointer dereference rather than a mess of UAFs. This may affect other protocols, I haven't checked. Signed-off-by: Daniel Axtens Reviewed-by: Daniel Kiper --- grub-core/net/net.c | 11 +++++++++-- grub-core/net/tftp.c | 1 + include/grub/net.h | 1 + 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/grub-core/net/net.c b/grub-core/net/net.c index d11672fbee..b8238b7df1 100644 --- a/grub-core/net/net.c +++ b/grub-core/net/net.c @@ -1546,7 +1546,8 @@ grub_net_fs_close (grub_file_t file) grub_netbuff_free (file->device->net->packs.first->nb); grub_net_remove_packet (file->device->net->packs.first); } - file->device->net->protocol->close (file); + if (!file->device->net->broken) + file->device->net->protocol->close (file); grub_free (file->device->net->name); return GRUB_ERR_NONE; } @@ -1768,7 +1769,10 @@ grub_net_seek_real (struct grub_file *file, grub_off_t offset) file->device->net->stall = 0; err = file->device->net->protocol->open (file, file->device->net->name); if (err) - return err; + { + file->device->net->broken = 1; + return err; + } grub_net_fs_read_real (file, NULL, offset); return grub_errno; } @@ -1777,6 +1781,9 @@ grub_net_seek_real (struct grub_file *file, grub_off_t offset) static grub_ssize_t grub_net_fs_read (grub_file_t file, char *buf, grub_size_t len) { + if (file->device->net->broken) + return -1; + if (file->offset != file->device->net->offset) { grub_err_t err; diff --git a/grub-core/net/tftp.c b/grub-core/net/tftp.c index f3e7879388..d1afa25352 100644 --- a/grub-core/net/tftp.c +++ b/grub-core/net/tftp.c @@ -404,6 +404,7 @@ tftp_open (struct grub_file *file, const char *filename) { grub_net_udp_close (data->sock); grub_free (data); + file->data = NULL; return grub_errno; } diff --git a/include/grub/net.h b/include/grub/net.h index cbcae79b1f..8d71ca6cc5 100644 --- a/include/grub/net.h +++ b/include/grub/net.h @@ -277,6 +277,7 @@ typedef struct grub_net grub_fs_t fs; int eof; int stall; + int broken; } *grub_net_t; extern grub_net_t (*EXPORT_VAR (grub_net_open)) (const char *name); -- 2.34.1