aboutsummaryrefslogtreecommitdiffstats
path: root/meta-networking/recipes-support/dnsmasq/files/CVE-2020-25681.patch
blob: 6756157700dd3b7df773f956c10f3374178ca21a (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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
From 4e96a4be685c9e4445f6ee79ad0b36b9119b502a Mon Sep 17 00:00:00 2001
From: Simon Kelley <simon@thekelleys.org.uk>
Date: Wed, 11 Nov 2020 23:25:04 +0000
Subject: [PATCH] Fix remote buffer overflow CERT VU#434904

The problem is in the sort_rrset() function and allows a remote
attacker to overwrite memory. Any dnsmasq instance with DNSSEC
enabled is vulnerable.

Signed-off-by: Sana Kazi <Sana.Kazi@kpit.com>
---
 CHANGELOG    |   7 +-
 src/dnssec.c | 273 ++++++++++++++++++++++++++++-----------------------
 2 files changed, 158 insertions(+), 122 deletions(-)

CVE: CVE-2020-25681
CVE: CVE-2020-25682
CVE: CVE-2020-25683
CVE: CVE-2020-25687
Upstream-Status: Backport [https://thekelleys.org.uk/gitweb/?p=dnsmasq.git;a=patch;h=4e96a4be685c9e4445f6ee79ad0b36b9119b502a]
Comment: Refreshed first two hunks

Index: dnsmasq-2.81/src/dnssec.c
===================================================================
--- dnsmasq-2.81.orig/src/dnssec.c
+++ dnsmasq-2.81/src/dnssec.c
@@ -223,138 +223,144 @@ static int check_date_range(unsigned lon
     && serial_compare_32(curtime, date_end) == SERIAL_LT;
 }
 
-/* Return bytes of canonicalised rdata, when the return value is zero, the remaining 
-   data, pointed to by *p, should be used raw. */
-static int get_rdata(struct dns_header *header, size_t plen, unsigned char *end, char *buff, int bufflen,
-		     unsigned char **p, u16 **desc)
+/* Return bytes of canonicalised rrdata one by one.
+   Init state->ip with the RR, and state->end with the end of same.
+   Init state->op to NULL.
+   Init state->desc to RR descriptor.
+   Init state->buff with a MAXDNAME * 2 buffer.
+   
+   After each call which returns 1, state->op points to the next byte of data.
+   On returning 0, the end has been reached.
+*/
+struct rdata_state {
+  u16 *desc;
+  size_t c;
+  unsigned char *end, *ip, *op;
+  char *buff;
+};
+
+static int get_rdata(struct dns_header *header, size_t plen, struct rdata_state *state)
 {
-  int d = **desc;
+  int d;
   
-  /* No more data needs mangling */
-  if (d == (u16)-1)
+  if (state->op && state->c != 1)
     {
-      /* If there's more data than we have space for, just return what fits,
-	 we'll get called again for more chunks */
-      if (end - *p > bufflen)
-	{
-	  memcpy(buff, *p, bufflen);
-	  *p += bufflen;
-	  return bufflen;
-	}
-      
-      return 0;
+      state->op++;
+      state->c--;
+      return 1;
     }
- 
-  (*desc)++;
-  
-  if (d == 0 && extract_name(header, plen, p, buff, 1, 0))
-    /* domain-name, canonicalise */
-    return to_wire(buff);
-  else
-    { 
-      /* plain data preceding a domain-name, don't run off the end of the data */
-      if ((end - *p) < d)
-	d = end - *p;
-      
-      if (d != 0)
+
+  while (1)
+    {
+      d = *(state->desc);
+      if (d == (u16)-1)
 	{
-	  memcpy(buff, *p, d);
-	  *p += d;
+         /* all the bytes to the end. */
+         if ((state->c = state->end - state->ip) != 0)
+           {
+             state->op = state->ip;
+             state->ip = state->end;;
+           }
+         else
+           return 0;
+       }
+      else
+       {
+         state->desc++;
+         
+         if (d == (u16)0)
+           {
+             /* domain-name, canonicalise */
+             int len;
+             
+             if (!extract_name(header, plen, &state->ip, state->buff, 1, 0) ||
+                 (len = to_wire(state->buff)) == 0)
+               continue;
+             
+             state->c = len;
+             state->op = (unsigned char *)state->buff;
+           }
+         else
+           {
+             /* plain data preceding a domain-name, don't run off the end of the data */
+             if ((state->end - state->ip) < d)
+               d = state->end - state->ip;
+             
+             if (d == 0)
+               continue;
+                 
+             state->op = state->ip;
+             state->c = d;
+             state->ip += d;
+           }
 	}
       
-      return d;
+      return 1;
     }
 }
 
-/* Bubble sort the RRset into the canonical order. 
-   Note that the byte-streams from two RRs may get unsynced: consider 
-   RRs which have two domain-names at the start and then other data.
-   The domain-names may have different lengths in each RR, but sort equal
-
-   ------------
-   |abcde|fghi|
-   ------------
-   |abcd|efghi|
-   ------------
-
-   leaving the following bytes as deciding the order. Hence the nasty left1 and left2 variables.
-*/
+/* Bubble sort the RRset into the canonical order. */
 
 static int sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int rrsetidx, 
 		      unsigned char **rrset, char *buff1, char *buff2)
 {
-  int swap, quit, i, j;
+  int swap, i, j;
   
   do
     {
       for (swap = 0, i = 0; i < rrsetidx-1; i++)
 	{
-	  int rdlen1, rdlen2, left1, left2, len1, len2, len, rc;
-	  u16 *dp1, *dp2;
-	  unsigned char *end1, *end2;
+         int rdlen1, rdlen2;
+         struct rdata_state state1, state2;
+         
 	  /* Note that these have been determined to be OK previously,
 	     so we don't need to check for NULL return here. */
-	  unsigned char *p1 = skip_name(rrset[i], header, plen, 10);
-	  unsigned char *p2 = skip_name(rrset[i+1], header, plen, 10);
-	  
-	  p1 += 8; /* skip class, type, ttl */
-	  GETSHORT(rdlen1, p1);
-	  end1 = p1 + rdlen1;
-	  
-	  p2 += 8; /* skip class, type, ttl */
-	  GETSHORT(rdlen2, p2);
-	  end2 = p2 + rdlen2; 
-	  
-	  dp1 = dp2 = rr_desc;
-	  
-	  for (quit = 0, left1 = 0, left2 = 0, len1 = 0, len2 = 0; !quit;)
+         state1.ip = skip_name(rrset[i], header, plen, 10);
+         state2.ip = skip_name(rrset[i+1], header, plen, 10);
+         state1.op = state2.op = NULL;
+         state1.buff = buff1;
+         state2.buff = buff2;
+         state1.desc = state2.desc = rr_desc;
+	  
+         state1.ip += 8; /* skip class, type, ttl */
+         GETSHORT(rdlen1, state1.ip);
+         if (!CHECK_LEN(header, state1.ip, plen, rdlen1))
+           return rrsetidx; /* short packet */
+         state1.end = state1.ip + rdlen1;
+         state2.ip += 8; /* skip class, type, ttl */
+         GETSHORT(rdlen2, state2.ip);
+         if (!CHECK_LEN(header, state2.ip, plen, rdlen2))
+           return rrsetidx; /* short packet */
+         state2.end = state2.ip + rdlen2;
+                 
+         while (1)
 	    {
-	      if (left1 != 0)
-		memmove(buff1, buff1 + len1 - left1, left1);
-	      
-	      if ((len1 = get_rdata(header, plen, end1, buff1 + left1, (MAXDNAME * 2) - left1, &p1, &dp1)) == 0)
-		{
-		  quit = 1;
-		  len1 = end1 - p1;
-		  memcpy(buff1 + left1, p1, len1);
+             int ok1, ok2;
+             ok1 = get_rdata(header, plen, &state1);
+             ok2 = get_rdata(header, plen, &state2);
+
+             if (!ok1 && !ok2)
+               {
+                 /* Two RRs are equal, remove one copy. RFC 4034, para 6.3 */
+                 for (j = i+1; j < rrsetidx-1; j++)
+                   rrset[j] = rrset[j+1];
+                 rrsetidx--;
+                 i--;
+                 break;
 		}
-	      len1 += left1;
-	      
-	      if (left2 != 0)
-		memmove(buff2, buff2 + len2 - left2, left2);
-	      
-	      if ((len2 = get_rdata(header, plen, end2, buff2 + left2, (MAXDNAME *2) - left2, &p2, &dp2)) == 0)
-		{
-		  quit = 1;
-		  len2 = end2 - p2;
-		  memcpy(buff2 + left2, p2, len2);
-		}
-	      len2 += left2;
-	       
-	      if (len1 > len2)
-		left1 = len1 - len2, left2 = 0, len = len2;
-	      else
-		left2 = len2 - len1, left1 = 0, len = len1;
-	      
-	      rc = (len == 0) ? 0 : memcmp(buff1, buff2, len);
-	      
-	      if (rc > 0 || (rc == 0 && quit && len1 > len2))
+             else if (ok1 && (!ok2 || *state1.op > *state2.op))
 		{
 		  unsigned char *tmp = rrset[i+1];
 		  rrset[i+1] = rrset[i];
 		  rrset[i] = tmp;
-		  swap = quit = 1;
-		}
-	      else if (rc == 0 && quit && len1 == len2)
-		{
-		  /* Two RRs are equal, remove one copy. RFC 4034, para 6.3 */
-		  for (j = i+1; j < rrsetidx-1; j++)
-		    rrset[j] = rrset[j+1];
-		  rrsetidx--;
-		  i--;
+		  swap = 1;
+		  break;
 		}
-	      else if (rc < 0)
-		quit = 1;
+	      else if (ok2 && (!ok1 || *state2.op > *state1.op))
+		break;
+	      
+	      /* arrive here when bytes are equal, go round the loop again
+		 and compare the next ones. */
 	    }
 	}
     } while (swap);
@@ -569,12 +575,15 @@ static int validate_rrset(time_t now, st
       wire_len = to_wire(keyname);
       hash->update(ctx, (unsigned int)wire_len, (unsigned char*)keyname);
       from_wire(keyname);
+
+#define RRBUFLEN 300 /* Most RRs are smaller than this. */
       
       for (i = 0; i < rrsetidx; ++i)
 	{
-	  int seg;
-	  unsigned char *end, *cp;
-	  u16 len, *dp;
+         int j;
+         struct rdata_state state;
+         u16 len;
+         unsigned char rrbuf[RRBUFLEN];
 	  
 	  p = rrset[i];
 	 	  
@@ -586,12 +595,11 @@ static int validate_rrset(time_t now, st
 	  /* if more labels than in RRsig name, hash *.<no labels in rrsig labels field>  4035 5.3.2 */
 	  if (labels < name_labels)
 	    {
-	      int k;
-	      for (k = name_labels - labels; k != 0; k--)
+	      for (j = name_labels - labels; j != 0; j--)
 		{
 		  while (*name_start != '.' && *name_start != 0)
 		    name_start++;
-		  if (k != 1 && *name_start == '.')
+		  if (j != 1 && *name_start == '.')
 		    name_start++;
 		}
 	      
@@ -612,24 +620,44 @@ static int validate_rrset(time_t now, st
 	  if (!CHECK_LEN(header, p, plen, rdlen))
 	    return STAT_BOGUS; 
 	  
-	  end = p + rdlen;
-	  
-	  /* canonicalise rdata and calculate length of same, use name buffer as workspace.
-	     Note that name buffer is twice MAXDNAME long in DNSSEC mode. */
-	  cp = p;
-	  dp = rr_desc;
-	  for (len = 0; (seg = get_rdata(header, plen, end, name, MAXDNAME * 2, &cp, &dp)) != 0; len += seg);
-	  len += end - cp;
-	  len = htons(len);
+	  /* canonicalise rdata and calculate length of same, use
+	     name buffer as workspace for get_rdata. */
+	  state.ip = p;
+	  state.op = NULL;
+	  state.desc = rr_desc;
+	  state.buff = name;
+	  state.end = p + rdlen;
+	  
+	  for (j = 0; get_rdata(header, plen, &state); j++)
+	    if (j < RRBUFLEN)
+	      rrbuf[j] = *state.op;
+
+	  len = htons((u16)j);
 	  hash->update(ctx, 2, (unsigned char *)&len); 
+
+	  /* If the RR is shorter than RRBUFLEN (most of them, in practice)
+	     then we can just digest it now. If it exceeds RRBUFLEN we have to
+	     go back to the start and do it in chunks. */
+	  if (j >= RRBUFLEN)
+	    {
+	      state.ip = p;
+	      state.op = NULL;
+	      state.desc = rr_desc;
+
+	      for (j = 0; get_rdata(header, plen, &state); j++)
+		{
+		   rrbuf[j] = *state.op;
+
+		   if (j == RRBUFLEN - 1)
+		     {
+		       hash->update(ctx, RRBUFLEN, rrbuf);
+		       j = -1;
+		     }
+		}
+	    }
 	  
-	  /* Now canonicalise again and digest. */
-	  cp = p;
-	  dp = rr_desc;
-	  while ((seg = get_rdata(header, plen, end, name, MAXDNAME * 2, &cp, &dp)))
-	    hash->update(ctx, seg, (unsigned char *)name);
-	  if (cp != end)
-	    hash->update(ctx, end - cp, cp);
+	  if (j != 0)
+	    hash->update(ctx, j, rrbuf);
 	}
      
       hash->digest(ctx, hash->digest_size, digest);