Merge branch 'v1.4' into v1.5
[librecmc/librecmc.git] / package / network / services / dnsmasq / patches / 270-dnssec-wildcards.patch
1 From 4fe6744a220eddd3f1749b40cac3dfc510787de6 Mon Sep 17 00:00:00 2001
2 From: Simon Kelley <simon@thekelleys.org.uk>
3 Date: Fri, 19 Jan 2018 12:26:08 +0000
4 Subject: [PATCH] DNSSEC fix for wildcard NSEC records. CVE-2017-15107
5  applies.
6
7 It's OK for NSEC records to be expanded from wildcards,
8 but in that case, the proof of non-existence is only valid
9 starting at the wildcard name, *.<domain> NOT the name expanded
10 from the wildcard. Without this check it's possible for an
11 attacker to craft an NSEC which wrongly proves non-existence
12 in a domain which includes a wildcard for NSEC.
13 ---
14  src/dnssec.c |  117 +++++++++++++++++++++++++++++++++++++++++++++++++++-------
15  2 files changed, 114 insertions(+), 15 deletions(-)
16
17 --- a/src/dnssec.c
18 +++ b/src/dnssec.c
19 @@ -424,15 +424,17 @@ static void from_wire(char *name)
20  static int count_labels(char *name)
21  {
22    int i;
23 -
24 +  char *p;
25 +  
26    if (*name == 0)
27      return 0;
28  
29 -  for (i = 0; *name; name++)
30 -    if (*name == '.')
31 +  for (p = name, i = 0; *p; p++)
32 +    if (*p == '.')
33        i++;
34  
35 -  return i+1;
36 +  /* Don't count empty first label. */
37 +  return *name == '.' ? i : i+1;
38  }
39  
40  /* Implement RFC1982 wrapped compare for 32-bit numbers */
41 @@ -1412,8 +1414,8 @@ static int hostname_cmp(const char *a, c
42      }
43  }
44  
45 -static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count,
46 -                                   char *workspace1, char *workspace2, char *name, int type, int *nons)
47 +static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsigned char **nsecs, unsigned char **labels, int nsec_count,
48 +                                   char *workspace1_in, char *workspace2, char *name, int type, int *nons)
49  {
50    int i, rc, rdlen;
51    unsigned char *p, *psave;
52 @@ -1426,6 +1428,9 @@ static int prove_non_existence_nsec(stru
53    /* Find NSEC record that proves name doesn't exist */
54    for (i = 0; i < nsec_count; i++)
55      {
56 +      char *workspace1 = workspace1_in;
57 +      int sig_labels, name_labels;
58 +
59        p = nsecs[i];
60        if (!extract_name(header, plen, &p, workspace1, 1, 10))
61         return 0;
62 @@ -1434,7 +1439,27 @@ static int prove_non_existence_nsec(stru
63        psave = p;
64        if (!extract_name(header, plen, &p, workspace2, 1, 10))
65         return 0;
66 -      
67 +
68 +      /* If NSEC comes from wildcard expansion, use original wildcard
69 +        as name for computation. */
70 +      sig_labels = *labels[i];
71 +      name_labels = count_labels(workspace1);
72 +
73 +      if (sig_labels < name_labels)
74 +       {
75 +         int k;
76 +         for (k = name_labels - sig_labels; k != 0; k--)
77 +           {
78 +             while (*workspace1 != '.' && *workspace1 != 0)
79 +               workspace1++;
80 +             if (k != 1 && *workspace1 == '.')
81 +               workspace1++;
82 +           }
83 +         
84 +         workspace1--;
85 +         *workspace1 = '*';
86 +       }
87 +         
88        rc = hostname_cmp(workspace1, name);
89        
90        if (rc == 0)
91 @@ -1832,24 +1857,26 @@ static int prove_non_existence_nsec3(str
92  
93  static int prove_non_existence(struct dns_header *header, size_t plen, char *keyname, char *name, int qtype, int qclass, char *wildname, int *nons)
94  {
95 -  static unsigned char **nsecset = NULL;
96 -  static int nsecset_sz = 0;
97 +  static unsigned char **nsecset = NULL, **rrsig_labels = NULL;
98 +  static int nsecset_sz = 0, rrsig_labels_sz = 0;
99    
100    int type_found = 0;
101 -  unsigned char *p = skip_questions(header, plen);
102 +  unsigned char *auth_start, *p = skip_questions(header, plen);
103    int type, class, rdlen, i, nsecs_found;
104    
105    /* Move to NS section */
106    if (!p || !(p = skip_section(p, ntohs(header->ancount), header, plen)))
107      return 0;
108 +
109 +  auth_start = p;
110    
111    for (nsecs_found = 0, i = ntohs(header->nscount); i != 0; i--)
112      {
113        unsigned char *pstart = p;
114        
115 -      if (!(p = skip_name(p, header, plen, 10)))
116 +      if (!extract_name(header, plen, &p, daemon->workspacename, 1, 10))
117         return 0;
118 -      
119 +         
120        GETSHORT(type, p); 
121        GETSHORT(class, p);
122        p += 4; /* TTL */
123 @@ -1866,7 +1893,69 @@ static int prove_non_existence(struct dn
124           if (!expand_workspace(&nsecset, &nsecset_sz, nsecs_found))
125             return 0; 
126           
127 -         nsecset[nsecs_found++] = pstart;
128 +         if (type == T_NSEC)
129 +           {
130 +             /* If we're looking for NSECs, find the corresponding SIGs, to 
131 +                extract the labels value, which we need in case the NSECs
132 +                are the result of wildcard expansion.
133 +                Note that the NSEC may not have been validated yet
134 +                so if there are multiple SIGs, make sure the label value
135 +                is the same in all, to avoid be duped by a rogue one.
136 +                If there are no SIGs, that's an error */
137 +             unsigned char *p1 = auth_start;
138 +             int res, j, rdlen1, type1, class1;
139 +             
140 +             if (!expand_workspace(&rrsig_labels, &rrsig_labels_sz, nsecs_found))
141 +               return 0;
142 +             
143 +             rrsig_labels[nsecs_found] = NULL;
144 +             
145 +             for (j = ntohs(header->nscount); j != 0; j--)
146 +               {
147 +                 if (!(res = extract_name(header, plen, &p1, daemon->workspacename, 0, 10)))
148 +                   return 0;
149 +
150 +                  GETSHORT(type1, p1); 
151 +                  GETSHORT(class1, p1);
152 +                  p1 += 4; /* TTL */
153 +                  GETSHORT(rdlen1, p1);
154 +
155 +                  if (!CHECK_LEN(header, p1, plen, rdlen1))
156 +                    return 0;
157 +                  
158 +                  if (res == 1 && class1 == qclass && type1 == T_RRSIG)
159 +                    {
160 +                      int type_covered;
161 +                      unsigned char *psav = p1;
162 +                      
163 +                      if (rdlen1 < 18)
164 +                        return 0; /* bad packet */
165 +
166 +                      GETSHORT(type_covered, p1);
167 +
168 +                      if (type_covered == T_NSEC)
169 +                        {
170 +                          p1++; /* algo */
171 +                          
172 +                          /* labels field must be the same in every SIG we find. */
173 +                          if (!rrsig_labels[nsecs_found])
174 +                            rrsig_labels[nsecs_found] = p1;
175 +                          else if (*rrsig_labels[nsecs_found] != *p1) /* algo */
176 +                            return 0;
177 +                          }
178 +                      p1 = psav;
179 +                    }
180 +                  
181 +                  if (!ADD_RDLEN(header, p1, plen, rdlen1))
182 +                    return 0;
183 +               }
184 +
185 +             /* Must have found at least one sig. */
186 +             if (!rrsig_labels[nsecs_found])
187 +               return 0;
188 +           }
189 +
190 +         nsecset[nsecs_found++] = pstart;   
191         }
192        
193        if (!ADD_RDLEN(header, p, plen, rdlen))
194 @@ -1874,7 +1963,7 @@ static int prove_non_existence(struct dn
195      }
196    
197    if (type_found == T_NSEC)
198 -    return prove_non_existence_nsec(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, nons);
199 +    return prove_non_existence_nsec(header, plen, nsecset, rrsig_labels, nsecs_found, daemon->workspacename, keyname, name, qtype, nons);
200    else if (type_found == T_NSEC3)
201      return prove_non_existence_nsec3(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, wildname, nons);
202    else