270-dnssec-wildcards.patch 6.4 KB

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