Skip to content

Commit 6b490f5

Browse files
committed
Fix crash on Apple platforms caused by concurrent libresolv calls
1 parent dc110f5 commit 6b490f5

4 files changed

Lines changed: 169 additions & 47 deletions

File tree

dns/transport/local/local.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ type Transport struct {
4040
resolved ResolvedResolver
4141
mdnsTransport adapter.DNSTransport
4242
dhcpTransport dhcpTransport
43+
systemResolver systemResolver
4344
neighborResolver adapter.NeighborResolver
4445
neighborSuffixes []string
4546
}
@@ -50,6 +51,12 @@ type dhcpTransport interface {
5051
Exchange0(ctx context.Context, message *mDNS.Msg, servers []M.Socksaddr) (*mDNS.Msg, error)
5152
}
5253

54+
type systemResolver interface {
55+
Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error)
56+
Reset()
57+
Close() error
58+
}
59+
5360
func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.LocalDNSServerOptions) (adapter.DNSTransport, error) {
5461
transportDialer, err := dns.NewLocalDialer(ctx, options)
5562
if err != nil {
@@ -91,6 +98,7 @@ func (t *Transport) Start(stage adapter.StartStage) error {
9198
}
9299
case adapter.StartStateStart:
93100
if C.IsDarwin {
101+
t.systemResolver = newSystemResolver()
94102
inboundManager := service.FromContext[adapter.InboundManager](t.ctx)
95103
for _, inbound := range inboundManager.Inbounds() {
96104
if inbound.Type() == C.TypeTun {
@@ -127,7 +135,7 @@ func (t *Transport) Start(stage adapter.StartStage) error {
127135
}
128136

129137
func (t *Transport) Close() error {
130-
return common.Close(t.resolved, t.dhcpTransport, t.mdnsTransport)
138+
return common.Close(t.resolved, t.dhcpTransport, t.mdnsTransport, t.systemResolver)
131139
}
132140

133141
func (t *Transport) Reset() {
@@ -137,6 +145,9 @@ func (t *Transport) Reset() {
137145
if t.mdnsTransport != nil {
138146
t.mdnsTransport.Reset()
139147
}
148+
if t.systemResolver != nil {
149+
t.systemResolver.Reset()
150+
}
140151
}
141152

142153
func (t *Transport) PreferredDomain(domain string) bool {
@@ -162,7 +173,7 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg,
162173
}
163174
if mdns.IsLocalDomain(question.Name) {
164175
if C.IsDarwin {
165-
return t.systemExchange(ctx, message)
176+
return t.systemResolver.Exchange(ctx, message)
166177
}
167178
return t.mdnsTransport.Exchange(ctx, message)
168179
}
@@ -176,7 +187,7 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg,
176187
}
177188
}
178189
if t.fallback {
179-
return t.systemExchange(ctx, message)
190+
return t.systemResolver.Exchange(ctx, message)
180191
}
181192
return t.exchange(ctx, message, question.Name)
182193
}

dns/transport/local/local_darwin_cgo.go

Lines changed: 140 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,14 @@ package local
77
#include <netdb.h>
88
#include <dns.h>
99
10-
static int cgo_dns_search(const char *name, int class, int type,
10+
static int cgo_dns_search(dns_handle_t handle, const char *name, int class, int type,
1111
unsigned char *answer, int anslen, int *out_h_errno) {
12-
dns_handle_t handle = (dns_handle_t)dns_open(NULL);
13-
if (handle == NULL) {
14-
*out_h_errno = NO_RECOVERY;
15-
return -1;
16-
}
1712
struct sockaddr_storage from;
1813
uint32_t fromlen = sizeof(from);
1914
h_errno = 0;
2015
int n = dns_search(handle, name, class, type, (char *)answer, anslen,
2116
(struct sockaddr *)&from, &fromlen);
2217
*out_h_errno = h_errno;
23-
dns_free(handle);
2418
return n;
2519
}
2620
*/
@@ -29,6 +23,8 @@ import "C"
2923
import (
3024
"context"
3125
"errors"
26+
"net"
27+
"sync"
3228
"unsafe"
3329

3430
"github.com/sagernet/sing-box/dns"
@@ -44,48 +40,115 @@ const (
4440
darwinResolverNoData = 4
4541
)
4642

47-
func darwinLookupSystemDNS(name string, class, qtype int) (*mDNS.Msg, error) {
48-
cName := C.CString(name)
49-
defer C.free(unsafe.Pointer(cName))
43+
type darwinDNSHandle struct {
44+
handle C.dns_handle_t
45+
generation uint64
46+
}
5047

51-
answer := make([]byte, 4096)
52-
var hErrno C.int
53-
n := C.cgo_dns_search(cName, C.int(class), C.int(qtype),
54-
(*C.uchar)(unsafe.Pointer(&answer[0])), C.int(len(answer)),
55-
&hErrno)
56-
if n <= 0 {
57-
return nil, darwinResolverHErrno(name, int(hErrno))
48+
// darwinSystemResolver pools libresolv handles. iOS 26.5.1 NULL-derefs in
49+
// libresolv when resolver state is rebuilt by concurrent dns_open(NULL) calls,
50+
// and dns_search corrupts state when two queries share one handle. Handles are
51+
// therefore reused across queries and leased exclusively: each in-flight query
52+
// owns one handle, dns_search runs concurrently on independent handles, while
53+
// dns_open and dns_free are serialized by lifecycleAccess. A leased handle is
54+
// removed from idle and owned by its caller, so Reset/Close only ever free idle
55+
// handles and the caller frees its own handle on release — dns_free never races
56+
// an in-flight dns_search and is never called twice for the same handle.
57+
type darwinSystemResolver struct {
58+
access sync.Mutex
59+
lifecycleAccess sync.Mutex
60+
idle []*darwinDNSHandle
61+
generation uint64
62+
closed bool
63+
}
64+
65+
func newSystemResolver() systemResolver {
66+
return &darwinSystemResolver{}
67+
}
68+
69+
func (r *darwinSystemResolver) acquire() (*darwinDNSHandle, error) {
70+
r.access.Lock()
71+
if r.closed {
72+
r.access.Unlock()
73+
return nil, net.ErrClosed
5874
}
59-
var response mDNS.Msg
60-
err := response.Unpack(answer[:int(n)])
61-
if err != nil {
62-
return nil, E.Cause(err, "unpack dns_search response")
75+
if count := len(r.idle); count > 0 {
76+
handle := r.idle[count-1]
77+
r.idle[count-1] = nil
78+
r.idle = r.idle[:count-1]
79+
r.access.Unlock()
80+
return handle, nil
6381
}
64-
return &response, nil
82+
generation := r.generation
83+
r.access.Unlock()
84+
85+
r.lifecycleAccess.Lock()
86+
cHandle := C.dns_open(nil)
87+
r.lifecycleAccess.Unlock()
88+
if cHandle == nil {
89+
return nil, dns.RcodeServerFailure
90+
}
91+
return &darwinDNSHandle{handle: cHandle, generation: generation}, nil
6592
}
6693

67-
func darwinResolverHErrno(name string, hErrno int) error {
68-
switch hErrno {
69-
case darwinResolverHostNotFound:
70-
return dns.RcodeNameError
71-
case darwinResolverNoData:
72-
return dns.RcodeSuccess
73-
case darwinResolverTryAgain, darwinResolverNoRecovery:
74-
return dns.RcodeServerFailure
75-
default:
76-
return E.New("dns_search: unknown h_errno ", hErrno, " for ", name)
94+
func (r *darwinSystemResolver) release(handle *darwinDNSHandle) {
95+
r.access.Lock()
96+
reuse := !r.closed && handle.generation == r.generation
97+
if reuse {
98+
r.idle = append(r.idle, handle)
99+
}
100+
r.access.Unlock()
101+
if !reuse {
102+
r.free(handle)
77103
}
78104
}
79105

80-
func (t *Transport) systemExchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
106+
func (r *darwinSystemResolver) free(handle *darwinDNSHandle) {
107+
r.lifecycleAccess.Lock()
108+
C.dns_free(handle.handle)
109+
r.lifecycleAccess.Unlock()
110+
}
111+
112+
func (r *darwinSystemResolver) Reset() {
113+
r.access.Lock()
114+
if r.closed {
115+
r.access.Unlock()
116+
return
117+
}
118+
r.generation++
119+
idle := r.idle
120+
r.idle = nil
121+
r.access.Unlock()
122+
for _, handle := range idle {
123+
r.free(handle)
124+
}
125+
}
126+
127+
func (r *darwinSystemResolver) Close() error {
128+
r.access.Lock()
129+
if r.closed {
130+
r.access.Unlock()
131+
return nil
132+
}
133+
r.closed = true
134+
idle := r.idle
135+
r.idle = nil
136+
r.access.Unlock()
137+
for _, handle := range idle {
138+
r.free(handle)
139+
}
140+
return nil
141+
}
142+
143+
func (r *darwinSystemResolver) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
81144
question := message.Question[0]
82145
type resolvResult struct {
83146
response *mDNS.Msg
84147
err error
85148
}
86149
resultCh := make(chan resolvResult, 1)
87150
go func() {
88-
response, err := darwinLookupSystemDNS(question.Name, int(question.Qclass), int(question.Qtype))
151+
response, err := r.lookup(question.Name, int(question.Qclass), int(question.Qtype))
89152
resultCh <- resolvResult{response, err}
90153
}()
91154
var result resolvResult
@@ -116,3 +179,46 @@ func (t *Transport) systemExchange(ctx context.Context, message *mDNS.Msg) (*mDN
116179
result.response.Response = true
117180
return result.response, nil
118181
}
182+
183+
func (r *darwinSystemResolver) lookup(name string, class, qtype int) (*mDNS.Msg, error) {
184+
handle, err := r.acquire()
185+
if err != nil {
186+
return nil, err
187+
}
188+
response, err := darwinSearch(handle.handle, name, class, qtype)
189+
r.release(handle)
190+
return response, err
191+
}
192+
193+
func darwinSearch(handle C.dns_handle_t, name string, class, qtype int) (*mDNS.Msg, error) {
194+
cName := C.CString(name)
195+
defer C.free(unsafe.Pointer(cName))
196+
197+
answer := make([]byte, 4096)
198+
var hErrno C.int
199+
n := C.cgo_dns_search(handle, cName, C.int(class), C.int(qtype),
200+
(*C.uchar)(unsafe.Pointer(&answer[0])), C.int(len(answer)),
201+
&hErrno)
202+
if n <= 0 {
203+
return nil, darwinResolverHErrno(name, int(hErrno))
204+
}
205+
var response mDNS.Msg
206+
err := response.Unpack(answer[:int(n)])
207+
if err != nil {
208+
return nil, E.Cause(err, "unpack dns_search response")
209+
}
210+
return &response, nil
211+
}
212+
213+
func darwinResolverHErrno(name string, hErrno int) error {
214+
switch hErrno {
215+
case darwinResolverHostNotFound:
216+
return dns.RcodeNameError
217+
case darwinResolverNoData:
218+
return dns.RcodeSuccess
219+
case darwinResolverTryAgain, darwinResolverNoRecovery:
220+
return dns.RcodeServerFailure
221+
default:
222+
return E.New("dns_search: unknown h_errno ", hErrno, " for ", name)
223+
}
224+
}

dns/transport/local/local_darwin_stun.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,18 @@ import (
1010
mDNS "github.com/miekg/dns"
1111
)
1212

13-
func (t *Transport) systemExchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
13+
func newSystemResolver() systemResolver {
14+
return &cgoRequiredResolver{}
15+
}
16+
17+
type cgoRequiredResolver struct{}
18+
19+
func (r *cgoRequiredResolver) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
1420
return nil, E.New(`local DNS server requires CGO on darwin, rebuild with CGO_ENABLED=1`)
1521
}
22+
23+
func (r *cgoRequiredResolver) Reset() {}
24+
25+
func (r *cgoRequiredResolver) Close() error {
26+
return nil
27+
}

dns/transport/local/local_other.go

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,6 @@
22

33
package local
44

5-
import (
6-
"context"
7-
"os"
8-
9-
mDNS "github.com/miekg/dns"
10-
)
11-
12-
func (t *Transport) systemExchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
13-
return nil, os.ErrInvalid
5+
func newSystemResolver() systemResolver {
6+
return nil
147
}

0 commit comments

Comments
 (0)