Hi,

Attached is a small patch for two of the three CVEs in this bug. I found no
upstream fix for either: dkim_qp_decode() is the same on upstream's develop
branch as in 2.11.0 Beta2, and dkim_canon_selecthdrs() there still has the
same loop. The patch applies to 2.11.0~beta2-9.2, 2.11.0~beta2-9.1 and
2.11.0~beta2-8+deb12u1 after their patch series. Bullseye was not checked.

CVE-2026-100888, dkim_canon_selecthdrs(): lhdrs has dkim_hdrcnt slots, but
the loop stores NULL at lhdrs[shcnt] for every name in h=. Once all headers
are used, shcnt equals dkim_hdrcnt, so one more name writes a NULL pointer
one slot past the array. The patch stops the loop at that point; no unused
header remains to be matched, so the result does not change.

CVE-2026-100889, dkim_qp_decode(): the check is q <= end with end = out +
outlen, so it stores outlen + 1 bytes. The callers in dkim.c that I read
all pass the buffer size minus one, so in those the extra byte is still
inside the buffer. It lands on the byte reserved for the terminator, and
the callers that ignore the return value can be left with an unterminated
string. I did not review other users of this exported function.

Testing is limited. I did not reproduce either bug and did not write a
trigger for them. I compiled the unpatched and patched functions with ASan
and UBSan in a small harness with stubbed types, and ran valid inputs
through them: 8 quoted-printable strings and 6 header lists, on all three
versions above. Output was identical and the sanitizers reported nothing.
That shows no regression on those inputs; it does not demonstrate the
fixes. I did not build the full package or run its test suite.

CVE-2026-101277 is not covered and I have not assessed it.

Regards,
Sumit Verma
Description: Fix one-slot overruns in dkim_canon_selecthdrs() and dkim_qp_decode()
 CVE-2026-100888: lhdrs has dkim_hdrcnt entries, but the loop stores NULL at
 lhdrs[shcnt] for each name in the signature's h= tag. Once every header has
 been used, shcnt equals dkim_hdrcnt and the next name writes past the array.
 Stop at that point; no unused header is left to match.
 .
 CVE-2026-100889: dkim_qp_decode() accepted q == end, storing one byte more
 than outlen. Use q < end.
Author: Sumit Verma <[email protected]>
Forwarded: no
---
--- a/libopendkim/dkim-canon.c
+++ b/libopendkim/dkim-canon.c
@@ -1049,6 +1049,10 @@
 	shcnt = 0;
 	for (c = 0; c < n; c++)
 	{
+		/* lhdrs has one slot per header; once all are used, stop */
+		if (shcnt >= dkim->dkim_hdrcnt)
+			break;
+
 		lhdrs[shcnt] = NULL;
 
 		len = MIN(DKIM_MAXHEADER, strlen((char *) hdrs[c]));
--- a/libopendkim/util.c
+++ b/libopendkim/util.c
@@ -364,7 +364,7 @@
 					{
 						decode++;
 
-						if (q <= end)
+						if (q < end)
 						{
 							*q = *x;
 							q++;
@@ -400,7 +400,7 @@
 				{
 					decode++;
 
-					if (q <= end)
+					if (q < end)
 					{
 						*q = *x;
 						q++;
@@ -410,7 +410,7 @@
 			start = NULL;
 			stop = NULL;
 
-			if (q <= end)
+			if (q < end)
 			{
 				*q = xl;
 				q++;
@@ -442,7 +442,7 @@
 				{
 					decode++;
 
-					if (q <= end)
+					if (q < end)
 					{
 						*q = *x;
 						q++;
@@ -454,7 +454,7 @@
 			{
 				decode++;
 
-				if (q <= end)
+				if (q < end)
 				{
 					*q = '\n';
 					q++;
@@ -463,12 +463,12 @@
 			else
 			{
 				decode += 2;
-				if (q <= end)
+				if (q < end)
 				{
 					*q = '\r';
 					q++;
 				}
-				if (q <= end)
+				if (q < end)
 				{
 					*q = '\n';
 					q++;
@@ -495,7 +495,7 @@
 		{
 			decode++;
 
-			if (q <= end)
+			if (q < end)
 			{
 				*q = *x;
 				q++;

Reply via email to