Merge ~sergiodj/ubuntu/+source/bind9:bug1997375-segfault-isc-nm-tcp into ubuntu/+source/bind9:ubuntu/focal-devel

Proposed by Sergio Durigan Junior
Status: Merged
Approved by: git-ubuntu bot
Approved revision: not available
Merged at revision: cd357eac4a7c77737bb0422d00a90a4847756553
Proposed branch: ~sergiodj/ubuntu/+source/bind9:bug1997375-segfault-isc-nm-tcp
Merge into: ubuntu/+source/bind9:ubuntu/focal-devel
Diff against target: 238 lines (+216/-0)
3 files modified
debian/changelog (+8/-0)
debian/patches/lp1997375-segfault-isc-nm-tcp-send.patch (+207/-0)
debian/patches/series (+1/-0)
Reviewer Review Type Date Requested Status
git-ubuntu bot Approve
Andreas Hasenack Approve
Canonical Server Reporter Pending
Review via email: mp+435153@code.launchpad.net

Description of the change

This MP fixes the segmentation fault experienced by some users on bug #1997375.

Unfortunately, I wasn't able to reliably reproduce the crash locally. It involves a complex scenario where bind9 is put under stress by having to handle a lot of requests and then there's a race condition on the tcpdns code (which is accessing socket internal fields in an unsafe manner), all of that finally leading to the segmentation fault.

This issue is affecting several community members, and they have been very helpful in determining the right fix. After one of them provided me with a coredump, I was able to inspect it and pinpoint what seemed to be the commit that fixes the problem:

https://gitlab.isc.org/isc-projects/bind9/-/commit/4ea84740e64f44ff1d397f1a317682633f174b0d

I backported it, put the package in a PPA and ask the community to try it out. As you can see in the bug report, I've had 3 people confirming that the segfaults don't happen with the patched bind9 anymore.

I decided to go ahead with the SRU and, again, rely on the community's good will to help me with the test plan.

There's a PPA with the proposed changes here:

https://launchpad.net/~sergiodj/+archive/ubuntu/bind9-bugfix/+packages

autopkgtest results:

Results: (from http://autopkgtest.ubuntu.com/results/autopkgtest-focal-sergiodj-bind9-bugfix/?format=plain)
  bind9 @ amd64:
    04.01.23 20:24:52 Log 🗒️ ✅ Triggers: bind9/1:9.16.1-0ubuntu2.12~ppa1
  bind9 @ arm64:
    04.01.23 20:27:21 Log 🗒️ ✅ Triggers: bind9/1:9.16.1-0ubuntu2.12~ppa1
  bind9 @ armhf:
    04.01.23 19:58:53 Log 🗒️ ✅ Triggers: bind9/1:9.16.1-0ubuntu2.12~ppa1
  bind9 @ ppc64el:
    04.01.23 20:28:12 Log 🗒️ ✅ Triggers: bind9/1:9.16.1-0ubuntu2.12~ppa1
  bind9 @ s390x:
    04.01.23 20:34:05 Log 🗒️ ✅ Triggers: bind9/1:9.16.1-0ubuntu2.12~ppa1

To post a comment you must log in.
Revision history for this message
Andreas Hasenack (ahasenack) wrote :

I'll look at this

Revision history for this message
Andreas Hasenack (ahasenack) :
Revision history for this message
Andreas Hasenack (ahasenack) wrote :

Hi Sergio,

I was following the trails of this bug in upstream gitlab.

The fix you are backporting comes from MR #3721: https://gitlab.isc.org/isc-projects/bind9/-/merge_requests/3721/commits

The commit is from June 16th, 2020.

There is this other MR #3726: https://gitlab.isc.org/isc-projects/bind9/-/merge_requests/3726/commits#882b07ab55f884856324e81fb113e650836a94d9

It shares the backported commit, but has two others on top:
commit 1cf65cd8829f01cc38f47b1180d7fbe3ab710d35
Author: Witold Kręcicki <email address hidden>
Date: Mon Jun 22 15:46:11 2020 -0700

    Fix a shutdown race in netmgr udp

    We need to mark the socket as inactive early (and synchronously)
    in the stoplistening process; otherwise we might destroy the
    callback argument before we actually stop listening, and call
    the callback on bad memory.

And

commit 3704c4fff2757ade6dda56865aa87935d0c447b9
Author: Evan Hunt <email address hidden>
Date: Sat Jun 20 15:03:05 2020 -0700

    clean up outerhandle when a tcpdns socket is disconnected

    this prevents a crash when some non-netmgr thread, such as a
    recursive lookup, times out after the TCP socket is already
    disconnected.

They also refer to the 1937 Issue (probably because of the shared commit), but also a new one, 1938: https://gitlab.isc.org/isc-projects/bind9/-/issues/1938

Issue 1938 says that because of the crash caused by 1937, bind was being restarted by systemd, and that was also crashing, which became issue 1938. Here is an interesting comment in 1938:

    The server that was repeatedly crashing on restart was eventually restarted successfully
    after stopping client query traffic and letting named load all zones undisturbed before
    allowing client queries again

It *looks* like being inundated with queries (what caused the 1937 crash) while starting up or recovering from the crash also causes problems, and this time it was in the new code.

These are complicated scenarios, and backports. I was wondering if you considered the 1938 issue, or if you hadn't seen it. The commits fixing 1938 are a few days later than the 1937 ones, in the git log.

review: Needs Information
Revision history for this message
Sergio Durigan Junior (sergiodj) wrote :

Thanks for the review, Andreas.

I hadn't noticed MR #3726; interesting. It does seem to be a more complete fix for a more generic bug, but it's also a bit more complex and, as such, introduces more possibilities for regressions.

I'm now wondering whether it makes sense to bite the bullet and backport everything, or keep the fix more "contained" and just address the issue raised in our bug report. Something to be noted is that at least 3 people tested the changes I'm proposing here and so far haven't experienced negative side effects.

As a member of the SRU team, what's your opinion here? I'm inclined to be more conservative and not backport the other commits from MR #3726, but I'm open to suggestions.

Revision history for this message
Andreas Hasenack (ahasenack) wrote :

As a member of the SRU team, I normally look ahead in upstream commits whenever a big chunk of C code is refactoring something or introducing new functions, and this is such a case, which led me to find what I did. I don't know what it would look like when backported, and whether it's worth the risk. That's part of the risk analysis of the SRU. At a glance, it looks like whatever flood of queries that is causing the crash keeps incoming while systemd is restarting bind, and that is what causes the second crash.

If we start noticing the second crash after the SRU is released, then the second fix can still be applied I suppose. I would just confirm with those 3 people who tested the ppa package if they had the bind9 service unit configured in such a way as to restart bind whenever it crashed, I don't know if that's our default or not.

Revision history for this message
Sergio Durigan Junior (sergiodj) wrote :

OK, back to this MP.

I talked to Andreas yesterday and decided to leave a comment upstream asking for their input regarding the two MRs. I've come to believe that we really just need MR 3721 (which is the one I backported here), because it is the upstream backport of MR 3726 (as can be seen in this comment: https://gitlab.isc.org/isc-projects/bind9/-/merge_requests/3721#note_143992). In fact, you can confirm that MR 3721 targets the v9_16 branch, while MR 3726 targets the main branch. But in any case, I think it's a good idea to confirm with them.

I won't bother getting in touch with the folks who have been helping me with testing this package in Ubuntu for now. Hopefully upstream's answer will be enough to get us to move forward here. If they say that I should backport more commits, then I'll prepare another package, put it in the PPA and ask for more testing. Otherwise, I'll ask Andreas to finish his review of this MP.

Revision history for this message
Sergio Durigan Junior (sergiodj) wrote :

Upstream's reply wasn't really encouraging:

https://gitlab.isc.org/isc-projects/bind9/-/merge_requests/3721#note_345089

While I agree with Ondřej's opinion, I also believe that we have to work with what's available for now. Hopefully Lena's effort to get an MRE for bind9 will bear fruit and we'll be able to have the proper, fuller fix for this problem shipped to our users, but meanwhile I believe it's justifiable to proceed with this MP as is.

Andreas, if you're OK with it I'd like to take my chances and upload this change to Focal.

Revision history for this message
Andreas Hasenack (ahasenack) wrote :

Ok, +1. Tiny optional nit, the DEP3 headers are missing Last-Update and Applied-Upstream. There is also that inline comment about perhaps changing the bug url to https://gitlab.isc.org/isc-projects/bind9/-/issues/1937, I don't know if you saw that, but it's also a nit.

review: Approve
Revision history for this message
git-ubuntu bot (git-ubuntu-bot) wrote :

Approvers: sergiodj, ahasenack
Uploaders: sergiodj, ahasenack
MP auto-approved

review: Approve
Revision history for this message
Sergio Durigan Junior (sergiodj) wrote :

On Thursday, January 19 2023, Andreas Hasenack wrote:

> Ok, +1. Tiny optional nit, the DEP3 headers are missing Last-Update
> and Applied-Upstream. There is also that inline comment about perhaps
> changing the bug url to
> https://gitlab.isc.org/isc-projects/bind9/-/issues/1937, I don't know
> if you saw that, but it's also a nit.

Thanks, Andreas.

I've addressed the points above and uploaded the package:

$ dput bind9_9.16.1-0ubuntu2.12_source.changes
Trying to upload package to ubuntu
Checking signature on .changes
gpg: /home/sergio/work/bind9/bind9_9.16.1-0ubuntu2.12_source.changes: Valid signature from 106DA1C8C3CBBF14
Checking signature on .dsc
gpg: /home/sergio/work/bind9/bind9_9.16.1-0ubuntu2.12.dsc: Valid signature from 106DA1C8C3CBBF14
Uploading to ubuntu (via ftp to upload.ubuntu.com):
  Uploading bind9_9.16.1-0ubuntu2.12.dsc: done.
  Uploading bind9_9.16.1-0ubuntu2.12.debian.tar.xz: done.
  Uploading bind9_9.16.1-0ubuntu2.12_source.buildinfo: done.
  Uploading bind9_9.16.1-0ubuntu2.12_source.changes: done.
Successfully uploaded packages.

--
Sergio
GPG key ID: E92F D0B3 6B14 F1F4 D8E0 EB2F 106D A1C8 C3CB BF14

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/debian/changelog b/debian/changelog
2index 277cb02..b487525 100644
3--- a/debian/changelog
4+++ b/debian/changelog
5@@ -1,3 +1,11 @@
6+bind9 (1:9.16.1-0ubuntu2.13) focal; urgency=medium
7+
8+ * d/p/lp1997375-segfault-isc-nm-tcp-send.patch: Fix segfault on
9+ isc__nm_tcpdns_send by moving the tcpdns processing to another
10+ thread. (LP: #1997375)
11+
12+ -- Sergio Durigan Junior <sergio.durigan@canonical.com> Thu, 02 Feb 2023 13:38:24 -0500
13+
14 bind9 (1:9.16.1-0ubuntu2.12) focal-security; urgency=medium
15
16 * SECURITY UPDATE: An UPDATE message flood may cause named to exhaust all
17diff --git a/debian/patches/lp1997375-segfault-isc-nm-tcp-send.patch b/debian/patches/lp1997375-segfault-isc-nm-tcp-send.patch
18new file mode 100644
19index 0000000..901bb43
20--- /dev/null
21+++ b/debian/patches/lp1997375-segfault-isc-nm-tcp-send.patch
22@@ -0,0 +1,207 @@
23+From: =?utf-8?q?Witold_Kr=C4=99cicki?= <wpk@isc.org>
24+Date: Tue, 16 Jun 2020 19:56:12 +0200
25+Subject: Make netmgr tcpdns send calls asynchronous.
26+
27+isc__nm_tcpdns_send() was not asynchronous and accessed socket
28+internal fields in an unsafe manner, which could lead to a race
29+condition and subsequent crash. Fix it by moving the whole tcpdns
30+processing to a proper netmgr thread.
31+
32+Origin: backport, https://gitlab.isc.org/isc-projects/bind9/-/commit/4ea84740e64f44ff1d397f1a317682633f174b0d
33+Bug: https://gitlab.isc.org/isc-projects/bind9/-/issues/1937
34+Bug-Ubuntu: https://bugs.launchpad.net/ubuntu/+source/bind9/+bug/1997375
35+Reviewed-By: Sergio Durigan Junior <sergiodj@ubuntu.com>
36+Last-Update: 2023-01-09
37+Applied-Upstream: 9.16.5
38+
39+---
40+ lib/isc/netmgr/netmgr-int.h | 5 +++
41+ lib/isc/netmgr/netmgr.c | 3 ++
42+ lib/isc/netmgr/tcpdns.c | 94 +++++++++++++++++++++++++++++----------------
43+ 3 files changed, 68 insertions(+), 34 deletions(-)
44+
45+diff --git a/lib/isc/netmgr/netmgr-int.h b/lib/isc/netmgr/netmgr-int.h
46+index 86b7015..0323b97 100644
47+--- a/lib/isc/netmgr/netmgr-int.h
48++++ b/lib/isc/netmgr/netmgr-int.h
49+@@ -138,6 +138,7 @@ typedef enum isc__netievent_type {
50+ netievent_tcpstop,
51+ netievent_tcpclose,
52+ netievent_tcpdnsclose,
53++ netievent_tcpdnssend,
54+ netievent_prio = 0xff, /* event type values higher than this
55+ * will be treated as high-priority
56+ * events, which can be processed
57+@@ -225,6 +226,7 @@ typedef struct isc__netievent__socket_req {
58+ typedef isc__netievent__socket_req_t isc__netievent_tcpconnect_t;
59+ typedef isc__netievent__socket_req_t isc__netievent_tcplisten_t;
60+ typedef isc__netievent__socket_req_t isc__netievent_tcpsend_t;
61++typedef isc__netievent__socket_req_t isc__netievent_tcpdnssend_t;
62+
63+ typedef struct isc__netievent__socket_streaminfo {
64+ isc__netievent_type type;
65+@@ -705,6 +707,9 @@ isc__nm_tcpdns_close(isc_nmsocket_t *sock);
66+ void
67+ isc__nm_async_tcpdnsclose(isc__networker_t *worker, isc__netievent_t *ev0);
68+
69++void
70++isc__nm_async_tcpdnssend(isc__networker_t *worker, isc__netievent_t *ev0);
71++
72+ #define isc__nm_uverr2result(x) \
73+ isc___nm_uverr2result(x, true, __FILE__, __LINE__)
74+ isc_result_t
75+diff --git a/lib/isc/netmgr/netmgr.c b/lib/isc/netmgr/netmgr.c
76+index e2cb89f..d5799c8 100644
77+--- a/lib/isc/netmgr/netmgr.c
78++++ b/lib/isc/netmgr/netmgr.c
79+@@ -615,6 +615,9 @@ process_queue(isc__networker_t *worker, isc_queue_t *queue) {
80+ case netievent_tcpsend:
81+ isc__nm_async_tcpsend(worker, ievent);
82+ break;
83++ case netievent_tcpdnssend:
84++ isc__nm_async_tcpdnssend(worker, ievent);
85++ break;
86+ case netievent_tcpstop:
87+ isc__nm_async_tcpstop(worker, ievent);
88+ break;
89+diff --git a/lib/isc/netmgr/tcpdns.c b/lib/isc/netmgr/tcpdns.c
90+index 11d3385..03bb9ce 100644
91+--- a/lib/isc/netmgr/tcpdns.c
92++++ b/lib/isc/netmgr/tcpdns.c
93+@@ -365,15 +365,6 @@ isc_nm_tcpdns_keepalive(isc_nmhandle_t *handle) {
94+ atomic_store(&handle->sock->outer->keepalive, true);
95+ }
96+
97+-typedef struct tcpsend {
98+- isc_mem_t *mctx;
99+- isc_nmhandle_t *handle;
100+- isc_region_t region;
101+- isc_nmhandle_t *orighandle;
102+- isc_nm_cb_t cb;
103+- void *cbarg;
104+-} tcpsend_t;
105+-
106+ static void
107+ resume_processing(void *arg) {
108+ isc_nmsocket_t *sock = (isc_nmsocket_t *)arg;
109+@@ -445,15 +436,40 @@ resume_processing(void *arg) {
110+
111+ static void
112+ tcpdnssend_cb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) {
113+- tcpsend_t *ts = (tcpsend_t *)cbarg;
114++ isc__nm_uvreq_t *req = (isc__nm_uvreq_t *)cbarg;
115+
116+ UNUSED(handle);
117+
118+- ts->cb(ts->orighandle, result, ts->cbarg);
119+- isc_mem_put(ts->mctx, ts->region.base, ts->region.length);
120++ req->cb.send(req->handle, result, req->cbarg);
121++ isc_mem_put(req->sock->mgr->mctx, req->uvbuf.base, req->uvbuf.len);
122++ isc__nm_uvreq_put(&req, req->handle->sock);
123++}
124++
125++void
126++isc__nm_async_tcpdnssend(isc__networker_t *worker, isc__netievent_t *ev0) {
127++ isc_result_t result;
128++ isc__netievent_tcpdnssend_t *ievent =
129++ (isc__netievent_tcpdnssend_t *)ev0;
130++ isc__nm_uvreq_t *req = ievent->req;
131++ isc_nmsocket_t *sock = ievent->sock;
132++
133++ REQUIRE(worker->id == sock->tid);
134+
135+- isc_nmhandle_unref(ts->orighandle);
136+- isc_mem_putanddetach(&ts->mctx, ts, sizeof(*ts));
137++ result = ISC_R_NOTCONNECTED;
138++ if (atomic_load(&sock->active)) {
139++ isc_region_t r;
140++
141++ r.base = (unsigned char *)req->uvbuf.base;
142++ r.length = req->uvbuf.len;
143++ result = isc__nm_tcp_send(sock->outer->tcphandle, &r,
144++ tcpdnssend_cb, req);
145++ }
146++
147++ if (result != ISC_R_SUCCESS) {
148++ req->cb.send(req->handle, result, req->cbarg);
149++ isc_mem_put(sock->mgr->mctx, req->uvbuf.base, req->uvbuf.len);
150++ isc__nm_uvreq_put(&req, sock);
151++ }
152+ }
153+
154+ /*
155+@@ -462,7 +478,7 @@ tcpdnssend_cb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) {
156+ isc_result_t
157+ isc__nm_tcpdns_send(isc_nmhandle_t *handle, isc_region_t *region,
158+ isc_nm_cb_t cb, void *cbarg) {
159+- tcpsend_t *t = NULL;
160++ isc__nm_uvreq_t *uvreq = NULL;
161+
162+ REQUIRE(VALID_NMHANDLE(handle));
163+
164+@@ -471,36 +487,46 @@ isc__nm_tcpdns_send(isc_nmhandle_t *handle, isc_region_t *region,
165+ REQUIRE(VALID_NMSOCK(sock));
166+ REQUIRE(sock->type == isc_nm_tcpdnssocket);
167+
168+- if (sock->outer == NULL) {
169+- /* The socket is closed */
170+- return (ISC_R_NOTCONNECTED);
171+- }
172++ uvreq = isc__nm_uvreq_get(sock->mgr, sock);
173++ uvreq->handle = handle;
174++ isc_nmhandle_ref(uvreq->handle);
175++ uvreq->cb.send = cb;
176++ uvreq->cbarg = cbarg;
177+
178+- t = isc_mem_get(sock->mgr->mctx, sizeof(*t));
179+- *t = (tcpsend_t){
180+- .cb = cb,
181+- .cbarg = cbarg,
182+- .handle = handle->sock->outer->tcphandle,
183+- };
184++ uvreq->uvbuf.base = isc_mem_get(sock->mgr->mctx, region->length + 2);
185++ uvreq->uvbuf.len = region->length + 2;
186++ *(uint16_t *)uvreq->uvbuf.base = htons(region->length);
187++ memmove(uvreq->uvbuf.base + 2, region->base, region->length);
188+
189+- isc_mem_attach(sock->mgr->mctx, &t->mctx);
190+- t->orighandle = handle;
191+- isc_nmhandle_ref(t->orighandle);
192++ if (sock->tid == isc_nm_tid()) {
193++ isc_region_t r;
194++
195++ r.base = (unsigned char *)uvreq->uvbuf.base;
196++ r.length = uvreq->uvbuf.len;
197+
198+- t->region = (isc_region_t){ .base = isc_mem_get(t->mctx,
199+- region->length + 2),
200+- .length = region->length + 2 };
201++ return (isc__nm_tcp_send(sock->outer->tcphandle, &r,
202++ tcpdnssend_cb, uvreq));
203++ } else {
204++ isc__netievent_tcpdnssend_t *ievent = NULL;
205+
206+- *(uint16_t *)t->region.base = htons(region->length);
207+- memmove(t->region.base + 2, region->base, region->length);
208++ ievent = isc__nm_get_ievent(sock->mgr, netievent_tcpdnssend);
209++ ievent->req = uvreq;
210++ ievent->sock = sock;
211+
212+- return (isc__nm_tcp_send(t->handle, &t->region, tcpdnssend_cb, t));
213++ isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
214++ (isc__netievent_t *)ievent);
215++
216++ return (ISC_R_SUCCESS);
217++ }
218++
219++ return (ISC_R_UNEXPECTED);
220+ }
221+
222+ static void
223+ tcpdns_close_direct(isc_nmsocket_t *sock) {
224+ REQUIRE(sock->tid == isc_nm_tid());
225+ /* We don't need atomics here, it's all in single network thread */
226++
227+ if (sock->timer_initialized) {
228+ /*
229+ * We need to fire the timer callback to clean it up,
230diff --git a/debian/patches/series b/debian/patches/series
231index c1e1ee1..965a609 100644
232--- a/debian/patches/series
233+++ b/debian/patches/series
234@@ -21,3 +21,4 @@ CVE-2022-2795.patch
235 CVE-2022-38177.patch
236 CVE-2022-38178.patch
237 CVE-2022-3094.patch
238+lp1997375-segfault-isc-nm-tcp-send.patch

Subscribers

People subscribed via source and target branches