summaryrefslogtreecommitdiffstats
path: root/debian/patches/0082-net-tftp-Prevent-a-UAF-and-double-free-from-a-failed.patch
blob: bb4cd8ca6d82a08787039d78a1d204ff75386460 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
From e7573be61b3cf005cdf0a068652153437daca4b3 Mon Sep 17 00:00:00 2001
From: Daniel Axtens <dja@axtens.net>
Date: Mon, 20 Sep 2021 01:12:24 +1000
Subject: 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 <dja@axtens.net>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
---
 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 15a2f29a9..af7440776 100644
--- a/grub-core/net/net.c
+++ b/grub-core/net/net.c
@@ -1548,7 +1548,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;
 }
@@ -1770,7 +1771,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;
   }
@@ -1779,6 +1783,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 f3e787938..d1afa2535 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 cbcae79b1..8d71ca6cc 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);