Skip to content

Commit fdab023

Browse files
authored
Merge pull request #17191 from miodvallat/50x.sa-2026-05
auth 5.0: backport SA 2026-05 fixes
2 parents 5c2a468 + db893cc commit fdab023

9 files changed

Lines changed: 197 additions & 105 deletions

File tree

ext/yahttp/yahttp/reqresp.cpp

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,19 @@ namespace YaHTTP {
4040
}
4141

4242
template <class T>
43-
bool AsyncLoader<T>::feed(const std::string& somedata) {
43+
bool AsyncLoader<T>::feed(const std::string& somedata)
44+
{
45+
if (state < 2) {
46+
headersize += somedata.length(); // maye include some body data, we don't know yet...
47+
if (headersize > target->max_header_size) {
48+
if (target->kind == YAHTTP_TYPE_REQUEST) {
49+
throw ParseError("Request header too large");
50+
}
51+
else {
52+
throw ParseError("Response header too large");
53+
}
54+
}
55+
}
4456
buffer.append(somedata);
4557
while(state < 2) {
4658
int cr=0;
@@ -155,8 +167,8 @@ namespace YaHTTP {
155167
maxbody = minbody;
156168
}
157169
if (minbody < 1) return true; // guess there isn't anything left.
158-
if (target->kind == YAHTTP_TYPE_REQUEST && static_cast<ssize_t>(minbody) > target->max_request_size) throw ParseError("Max request body size exceeded");
159-
else if (target->kind == YAHTTP_TYPE_RESPONSE && static_cast<ssize_t>(minbody) > target->max_response_size) throw ParseError("Max response body size exceeded");
170+
if (target->kind == YAHTTP_TYPE_REQUEST && minbody > target->max_request_size) throw ParseError("Max request body size exceeded");
171+
else if (target->kind == YAHTTP_TYPE_RESPONSE && minbody > target->max_response_size) throw ParseError("Max response body size exceeded");
160172
}
161173

162174
if (maxbody == 0) hasBody = false;
@@ -175,20 +187,23 @@ namespace YaHTTP {
175187
buffer.copy(buf, pos);
176188
buf[pos]=0; // just in case...
177189
buffer.erase(buffer.begin(), buffer.begin()+pos+1); // remove line from buffer
178-
if (sscanf(buf, "%x", &chunk_size) != 1) {
190+
if (sscanf(buf, "%zx", &chunk_size) != 1) {
179191
throw ParseError("Unable to parse chunk size");
180192
}
181193
if (chunk_size == 0) { state = 3; break; } // last chunk
182-
if (chunk_size > (std::numeric_limits<decltype(chunk_size)>::max() - 2)) {
194+
if (chunk_size > (std::numeric_limits<decltype(chunk_size)>::max() - 2) || chunk_size > maxbody) {
183195
throw ParseError("Chunk is too large");
184196
}
185197
} else {
186198
int crlf=1;
187-
if (buffer.size() < static_cast<size_t>(chunk_size+1)) return false; // expect newline
199+
if (buffer.size() < chunk_size+1) return false; // expect newline
188200
if (buffer.at(chunk_size) == '\r') {
189-
if (buffer.size() < static_cast<size_t>(chunk_size+2) || buffer.at(chunk_size+1) != '\n') return false; // expect newline after carriage return
201+
if (buffer.size() < chunk_size+2 || buffer.at(chunk_size+1) != '\n') return false; // expect newline after carriage return
190202
crlf=2;
191203
} else if (buffer.at(chunk_size) != '\n') return false;
204+
if (bodybuf.str().length() + chunk_size > maxbody) {
205+
throw ParseError("Chunked body is too large");
206+
}
192207
std::string tmp = buffer.substr(0, chunk_size);
193208
buffer.erase(buffer.begin(), buffer.begin()+chunk_size+crlf);
194209
bodybuf << tmp;

ext/yahttp/yahttp/reqresp.hpp

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ namespace funcptr = boost;
2020

2121
#include <algorithm>
2222

23+
#ifndef YAHTTP_MAX_HEADER_SIZE
24+
#define YAHTTP_MAX_HEADER_SIZE (100 * 1024)
25+
#endif
26+
2327
#ifndef YAHTTP_MAX_REQUEST_SIZE
2428
#define YAHTTP_MAX_REQUEST_SIZE 2097152
2529
#endif
@@ -108,6 +112,7 @@ namespace YaHTTP {
108112
#endif
109113
max_request_size = YAHTTP_MAX_REQUEST_SIZE;
110114
max_response_size = YAHTTP_MAX_RESPONSE_SIZE;
115+
max_header_size = YAHTTP_MAX_HEADER_SIZE;
111116
url = "";
112117
method = "";
113118
statusText = "";
@@ -130,6 +135,7 @@ namespace YaHTTP {
130135
this->parameters = rhs.parameters; this->getvars = rhs.getvars;
131136
this->body = rhs.body; this->max_request_size = rhs.max_request_size;
132137
this->max_response_size = rhs.max_response_size; this->version = rhs.version;
138+
this->max_header_size = rhs.max_header_size;
133139
#ifdef HAVE_CPP_FUNC_PTR
134140
this->renderer = rhs.renderer;
135141
#endif
@@ -143,6 +149,7 @@ namespace YaHTTP {
143149
this->parameters = rhs.parameters; this->getvars = rhs.getvars;
144150
this->body = rhs.body; this->max_request_size = rhs.max_request_size;
145151
this->max_response_size = rhs.max_response_size; this->version = rhs.version;
152+
this->max_header_size = rhs.max_header_size;
146153
#ifdef HAVE_CPP_FUNC_PTR
147154
this->renderer = rhs.renderer;
148155
#endif
@@ -166,8 +173,9 @@ namespace YaHTTP {
166173

167174
std::string body; //<! the actual content
168175

169-
ssize_t max_request_size; //<! maximum size of request
170-
ssize_t max_response_size; //<! maximum size of response
176+
size_t max_request_size; //<! maximum size of request
177+
size_t max_response_size; //<! maximum size of response
178+
size_t max_header_size; //<! maximum size of headers
171179
bool is_multipart; //<! if the request is multipart, prevents Content-Length header
172180
#ifdef HAVE_CPP_FUNC_PTR
173181
funcptr::function<size_t(const HTTPBase*,std::ostream&,bool)> renderer; //<! rendering function
@@ -301,10 +309,11 @@ namespace YaHTTP {
301309

302310
std::string buffer; //<! read buffer
303311
bool chunked; //<! whether we are parsing chunked data
304-
int chunk_size; //<! expected size of next chunk
312+
size_t chunk_size; //<! expected size of next chunk
305313
std::ostringstream bodybuf; //<! buffer for body
306314
size_t maxbody; //<! maximum size of body
307315
size_t minbody; //<! minimum size of body
316+
size_t headersize;
308317
bool hasBody; //<! are we expecting body
309318

310319
void keyValuePair(const std::string &keyvalue, std::string &key, std::string &value); //<! key value pair parser helper
@@ -315,6 +324,7 @@ namespace YaHTTP {
315324
pos = 0; state = 0; this->target = target_;
316325
hasBody = false;
317326
buffer = "";
327+
headersize = 0;
318328
this->target->initialize();
319329
}; //<! Initialize the parser for target and clear state
320330
bool feed(const std::string& somedata); //<! Feed data to the parser

modules/bindbackend/bindbackend2.cc

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1492,7 +1492,29 @@ BB2DomainInfo Bind2Backend::createDomainEntry(const ZoneName& domain, const stri
14921492

14931493
bool Bind2Backend::createSecondaryDomain(const string& ipAddress, const ZoneName& domain, const string& /* nameserver */, const string& account)
14941494
{
1495-
string filename = getArg("autoprimary-destdir") + '/' + domain.toStringNoDot();
1495+
std::string domainname = domain.toStringNoDot();
1496+
1497+
// Reject domain name if it embeds quotes; this may happen if 8bit-dns is
1498+
// used, and bind currently does not allow for character escapes in zone
1499+
// names.
1500+
if (domainname.find_first_of("\"") != std::string::npos) {
1501+
SLOG(g_log << Logger::Error << d_logprefix
1502+
<< " Unable to accept autosecondary zone '" << domain
1503+
<< "' from autoprimary " << ipAddress
1504+
<< " due to unauthorized characters in domain name for bind configuration file"
1505+
<< endl,
1506+
d_slog->error(Logr::Error, "unauthorized characters in domain name for bind configuration file", "Unable to accept autosecondary zone", "zone", Logging::Loggable(domain), "autoprimary address", Logging::Loggable(ipAddress)));
1507+
throw PDNSException("Unauthorized characters in domain name for bind configuration file");
1508+
}
1509+
1510+
string filename = getArg("autoprimary-destdir") + '/';
1511+
if (domainname.empty()) {
1512+
filename.append("rootzone.");
1513+
}
1514+
else {
1515+
// Make sure the zone file name does not contain path separators.
1516+
filename.append(boost::replace_all_copy(domainname, "/", "\\047"));
1517+
}
14961518

14971519
g_log << Logger::Warning << d_logprefix
14981520
<< " Writing bind config zone statement for superslave zone '" << domain
@@ -1508,8 +1530,8 @@ bool Bind2Backend::createSecondaryDomain(const string& ipAddress, const ZoneName
15081530
}
15091531

15101532
c_of << endl;
1511-
c_of << "# AutoSecondary zone '" << domain.toString() << "' (added: " << nowTime() << ") (account: " << account << ')' << endl;
1512-
c_of << "zone \"" << domain.toStringNoDot() << "\" {" << endl;
1533+
c_of << "# AutoSecondary zone '" << domainname << "' (added: " << nowTime() << ") (account: " << account << ')' << endl;
1534+
c_of << "zone \"" << domainname << "\" {" << endl;
15131535
c_of << "\ttype secondary;" << endl;
15141536
c_of << "\tfile \"" << filename << "\";" << endl;
15151537
c_of << "\tprimaries { " << ipAddress << "; };" << endl;

modules/ldapbackend/native.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ void LdapBackend::lookup_tree(const QType& qtype, const DNSName& qname, DNSPacke
281281

282282
stringtok(parts, toLower(qname.toString()), ".");
283283
for (auto i = parts.crbegin(); i != parts.crend(); i++) {
284-
dn = "dc=" + *i + "," + dn;
284+
dn = "dc=" + d_pldap->escape(*i) + "," + dn;
285285
}
286286

287287
g_log << Logger::Debug << d_myname << " Search = basedn: " << dn + getArg("basedn") << ", filter: " << filter << ", qtype: " << qtype.toString() << endl;

modules/ldapbackend/powerldap.cc

Lines changed: 61 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -392,22 +392,68 @@ const string PowerLDAP::getError(int rc)
392392
return ldapGetError(d_ld, rc);
393393
}
394394

395-
const string PowerLDAP::escape(const string& str)
395+
// Escape sensitive characters according to the rules in RFC4514, section 2.4
396+
// and RFC4515, section 3.
397+
const std::string PowerLDAP::escape(const string& input)
396398
{
397-
string a;
398-
string::const_iterator i;
399-
char tmp[4];
400-
401-
for (i = str.begin(); i != str.end(); i++) {
402-
// RFC4515 3
403-
if ((unsigned char)*i == '*' || (unsigned char)*i == '(' || (unsigned char)*i == ')' || (unsigned char)*i == '\\' || (unsigned char)*i == '\0' || (unsigned char)*i > 127) {
404-
snprintf(tmp, sizeof(tmp), "\\%02x", (unsigned char)*i);
405-
406-
a += tmp;
399+
std::string out;
400+
std::array<char, 1 + 3> hexbuf{};
401+
auto length = input.length();
402+
403+
out.reserve(length);
404+
for (decltype(length) pos = 0; pos < length; ++pos) {
405+
uint8_t chr = static_cast<uint8_t>(input[pos]);
406+
// Perform UTF-8 encoding of 8-bit values if needed
407+
if (chr >= 0x80) {
408+
::snprintf(hexbuf.data(), hexbuf.size(), "\\%02X",
409+
static_cast<uint8_t>(0xc0 | ((chr >> 6) & 0x3f)));
410+
out.append(hexbuf.data());
411+
::snprintf(hexbuf.data(), hexbuf.size(), "\\%02X",
412+
static_cast<uint8_t>(0x80 | (chr & 0x3f)));
413+
out.append(hexbuf.data());
414+
}
415+
else {
416+
bool escape4514{false};
417+
bool escape4515{false};
418+
// Characters which need escaping regardless of their position
419+
switch (chr) {
420+
case '"':
421+
case '+':
422+
case ',':
423+
case ';':
424+
case '<':
425+
case '>':
426+
escape4514 = true;
427+
break;
428+
case '*':
429+
case '(':
430+
case ')':
431+
case '\\':
432+
case '\0':
433+
escape4515 = true;
434+
break;
435+
default:
436+
break;
437+
}
438+
// Characters which need escaping if in first position
439+
if (pos == 0) {
440+
escape4514 |= chr == ' ' || chr == '#';
441+
}
442+
// Characters which need escaping if in last position
443+
if (pos == length - 1) {
444+
escape4514 |= chr == ' ';
445+
}
446+
if (escape4515) {
447+
::snprintf(hexbuf.data(), hexbuf.size(), "\\%02X", chr);
448+
out.append(hexbuf.data());
449+
}
450+
else {
451+
if (escape4514) {
452+
out.append(1, '\\');
453+
}
454+
out.append(1, chr);
455+
}
407456
}
408-
else
409-
a += *i;
410457
}
411-
412-
return a;
458+
return out;
413459
}

pdns/dnswriter.cc

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -198,8 +198,12 @@ template <typename Container> void GenericDNSPacketWriter<Container>::xfrUnquote
198198
d_content.push_back(0);
199199
return;
200200
}
201-
if(lenField)
201+
if (lenField) {
202+
if (text.length() > 255) {
203+
throw runtime_error("invalid unquoted text length");
204+
}
202205
d_content.push_back(text.length());
206+
}
203207
d_content.insert(d_content.end(), text.c_str(), text.c_str() + text.length());
204208
}
205209

@@ -410,11 +414,14 @@ template <typename Container> void GenericDNSPacketWriter<Container>::xfrSvcPara
410414
break;
411415
case SvcParam::alpn:
412416
{
413-
uint16_t totalSize = param.getALPN().size(); // All 1 octet size headers for each value
417+
size_t totalSize = param.getALPN().size(); // All 1 octet size headers for each value
414418
for (auto const &a : param.getALPN()) {
415419
totalSize += a.length();
416420
}
417-
xfr16BitInt(totalSize);
421+
if (totalSize > std::numeric_limits<uint16_t>::max()) {
422+
throw runtime_error("invalid total length of alpn parameters");
423+
}
424+
xfr16BitInt(static_cast<uint16_t>(totalSize));
418425
for (auto const &a : param.getALPN()) {
419426
xfrUnquotedText(a, true); // will add the 1-byte length field
420427
}

pdns/rcpgenerator.cc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,9 @@ void RecordTextReader::xfrSvcParamKeyVals(set<SvcParam>& val) // NOLINT(readabil
435435
if (len == 0) {
436436
throw RecordTextException("ALPN values cannot be empty strings");
437437
}
438+
if (len > 255) {
439+
throw RecordTextException("Length of ALPN value goes over 255");
440+
}
438441
if (len > v.length() - spos) {
439442
throw RecordTextException("Length of ALPN value goes over total length of alpn SVC Param");
440443
}
@@ -443,6 +446,11 @@ void RecordTextReader::xfrSvcParamKeyVals(set<SvcParam>& val) // NOLINT(readabil
443446
}
444447
} else {
445448
xfrSVCBValueList(value);
449+
for (const auto& item : value) {
450+
if (item.length() > 255) {
451+
throw RecordTextException("Length of SVC ALPN value goes over 255");
452+
}
453+
}
446454
}
447455
if (value.empty()) {
448456
throw RecordTextException("value is required for SVC Param " + k);

0 commit comments

Comments
 (0)