]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Rewrite the ldap escape function, and always escape network-controlled input.
authorMiod Vallat <miod.vallat@powerdns.com>
Wed, 22 Apr 2026 07:30:16 +0000 (09:30 +0200)
committerMiod Vallat <miod.vallat@powerdns.com>
Wed, 22 Apr 2026 11:21:58 +0000 (13:21 +0200)
This new version now will correctly handle 8-bit characters (which need to
be encoded in UTF-8 and then escaped), as well as the corner cases of
leading space or # and trailing space.

This is CVE-2026-33609, part of PowerDNS Security Advisory 2026-05.

Signed-off-by: Miod Vallat <miod.vallat@powerdns.com>
modules/ldapbackend/native.cc
modules/ldapbackend/powerldap.cc

index 1b8815d6ac400ad07060b71400cedc47d5af0b72..99edc40ebedfc46c71c7bedc8ad27571ad23aa07 100644 (file)
@@ -297,7 +297,7 @@ void LdapBackend::lookup_tree(const QType& qtype, const DNSName& qname, DNSPacke
 
   stringtok(parts, toLower(qname.toString()), ".");
   for (auto i = parts.crbegin(); i != parts.crend(); i++) {
-    dn = "dc=" + *i + "," + dn;
+    dn = "dc=" + d_pldap->escape(*i) + "," + dn;
   }
 
   SLOG(g_log << Logger::Debug << d_myname << " Search = basedn: " << dn + getArg("basedn") << ", filter: " << filter << ", qtype: " << qtype.toString() << endl,
index 0edcbf9ef9a5622a6ed222db18dd37827d50ad50..9c7351ece0574bbd4cfd50e85f83c8085f5bd3cc 100644 (file)
@@ -328,22 +328,68 @@ const string PowerLDAP::getError(int rc)
   return ldapGetError(d_ld, rc);
 }
 
-const string PowerLDAP::escape(const string& str)
+// Escape sensitive characters according to the rules in RFC4514, section 2.4
+// and RFC4515, section 3.
+const std::string PowerLDAP::escape(const string& input)
 {
-  string a;
-  string::const_iterator i;
-  char tmp[4];
-
-  for (i = str.begin(); i != str.end(); i++) {
-    // RFC4515 3
-    if ((unsigned char)*i == '*' || (unsigned char)*i == '(' || (unsigned char)*i == ')' || (unsigned char)*i == '\\' || (unsigned char)*i == '\0' || (unsigned char)*i > 127) {
-      snprintf(tmp, sizeof(tmp), "\\%02x", (unsigned char)*i);
-
-      a += tmp;
+  std::string out;
+  std::array<char, 1 + 3> hexbuf{};
+  auto length = input.length();
+
+  out.reserve(length);
+  for (decltype(length) pos = 0; pos < length; ++pos) {
+    uint8_t chr = static_cast<uint8_t>(input[pos]);
+    // Perform UTF-8 encoding of 8-bit values if needed
+    if (chr >= 0x80) {
+      ::snprintf(hexbuf.data(), hexbuf.size(), "\\%02X",
+                 static_cast<uint8_t>(0xc0 | ((chr >> 6) & 0x3f)));
+      out.append(hexbuf.data());
+      ::snprintf(hexbuf.data(), hexbuf.size(), "\\%02X",
+                 static_cast<uint8_t>(0x80 | (chr & 0x3f)));
+      out.append(hexbuf.data());
+    }
+    else {
+      bool escape4514{false};
+      bool escape4515{false};
+      // Characters which need escaping regardless of their position
+      switch (chr) {
+      case '"':
+      case '+':
+      case ',':
+      case ';':
+      case '<':
+      case '>':
+        escape4514 = true;
+        break;
+      case '*':
+      case '(':
+      case ')':
+      case '\\':
+      case '\0':
+        escape4515 = true;
+        break;
+      default:
+        break;
+      }
+      // Characters which need escaping if in first position
+      if (pos == 0) {
+        escape4514 |= chr == ' ' || chr == '#';
+      }
+      // Characters which need escaping if in last position
+      if (pos == length - 1) {
+        escape4514 |= chr == ' ';
+      }
+      if (escape4515) {
+        ::snprintf(hexbuf.data(), hexbuf.size(), "\\%02X", chr);
+        out.append(hexbuf.data());
+      }
+      else {
+        if (escape4514) {
+          out.append(1, '\\');
+        }
+        out.append(1, chr);
+      }
     }
-    else
-      a += *i;
   }
-
-  return a;
+  return out;
 }