Skip to content

Commit bb19914

Browse files
committed
Merge branch 'maint'
* maint: Do not build binary until needed Do not drop random mode after getting econnrefused Be prepared for broken UTF-8 Do not presume that the caller has checked the QR field Compare MAC:s in constant time Fix MAC size calculation to consistently use bytes Check supported algs list before asking crypto for size Fix decode of truncated messages to throw the approproate error code Rework decode_name to cap the decoded length according to RFC 1035
2 parents 70a5a1c + 85c37c8 commit bb19914

4 files changed

Lines changed: 307 additions & 80 deletions

File tree

lib/kernel/src/inet_dns.erl

Lines changed: 92 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ decode_reply(Buffer, #dns_rec{} = Q, Mdns)
173173
{error, Reason}
174174
end.
175175

176+
-define(MSG_HDR_SIZE, 12). % Must align with the following pattern
176177
do_decode(
177178
<<Id:16,
178179
QR:1,Opcode:4,AA:1,TC:1,RD:1,
@@ -196,14 +197,19 @@ do_decode(
196197
pr = decode_boolean(PR),
197198
rcode = Rcode},
198199
do_decode(
199-
Buffer, DnsHdr, QdList, AnBuf, AnCount, NsCount, ArCount, {Opcode,Mdns}).
200+
Buffer, DnsHdr, QdList, AnBuf, AnCount, NsCount, ArCount, {Opcode,Mdns});
201+
do_decode(<<_/binary>>, _Mdns) ->
202+
throw(?DECODE_ERROR).
203+
200204

201205
do_decode_reply(
202206
<<Id:16, _/binary>> = Buffer,
203207
#dns_rec{ header = Q_H, qdlist = [Q_RR] },
204208
Mdns) ->
205209
Id =:= Q_H#dns_header.id orelse throw(badid),
206-
do_decode_reply(Buffer, Q_H, Q_RR, Id, Mdns).
210+
do_decode_reply(Buffer, Q_H, Q_RR, Id, Mdns);
211+
do_decode_reply(<<_/binary>>, _Q, _Mdns) ->
212+
throw(?DECODE_ERROR).
207213

208214
do_decode_reply(
209215
<<_:16,
@@ -223,27 +229,33 @@ do_decode_reply(
223229
%%
224230
QdCount == 1
225231
orelse throw(noquery),
226-
{AnBuf, [RR], QdTC} = decode_query_section(QdBuf, QdCount, Buffer, Mdns),
227-
RR#dns_query.class =:= Q_RR#dns_query.class andalso
228-
RR#dns_query.type =:= Q_RR#dns_query.type andalso
229-
inet_db:eq_domains(RR#dns_query.domain, Q_RR#dns_query.domain)
230-
orelse throw(noquery),
231-
H_TC = decode_boolean(TC),
232-
QdTC andalso not H_TC
233-
andalso throw(?DECODE_ERROR),
234-
DnsHdr =
235-
#dns_header{
236-
id = Id,
237-
qr = H_QR,
238-
opcode = H_Opcode,
239-
aa = decode_boolean(AA),
240-
tc = H_TC,
241-
rd = H_RD,
242-
ra = decode_boolean(RA),
243-
pr = decode_boolean(PR),
244-
rcode = Rcode},
245-
do_decode(
246-
Buffer, DnsHdr, [RR], AnBuf, AnCount, NsCount, ArCount, {Opcode,Mdns});
232+
{AnBuf, RRs, QdTC} = decode_query_section(QdBuf, QdCount, Buffer, Mdns),
233+
case RRs of
234+
[RR] ->
235+
RR#dns_query.class =:= Q_RR#dns_query.class andalso
236+
RR#dns_query.type =:= Q_RR#dns_query.type andalso
237+
inet_db:eq_domains(RR#dns_query.domain, Q_RR#dns_query.domain)
238+
orelse throw(noquery),
239+
H_TC = decode_boolean(TC),
240+
QdTC andalso not H_TC
241+
andalso throw(?DECODE_ERROR),
242+
DnsHdr =
243+
#dns_header{
244+
id = Id,
245+
qr = H_QR,
246+
opcode = H_Opcode,
247+
aa = decode_boolean(AA),
248+
tc = H_TC,
249+
rd = H_RD,
250+
ra = decode_boolean(RA),
251+
pr = decode_boolean(PR),
252+
rcode = Rcode},
253+
do_decode(
254+
Buffer, DnsHdr, [RR], AnBuf, AnCount, NsCount, ArCount,
255+
{Opcode,Mdns});
256+
_ ->
257+
throw(?DECODE_ERROR)
258+
end;
247259
do_decode_reply(<<_/binary>>, _Q_H, _Q_RR, _Id, _Mdns) ->
248260
throw(unknown).
249261

@@ -776,7 +788,11 @@ decode_characters(Data, Encoding) ->
776788
?MATCH_ELSE_DECODE_ERROR(
777789
Data,
778790
<<Len,Bin:Len/binary,Rest/binary>>,
779-
{Rest,unicode:characters_to_list(Bin, Encoding)}).
791+
?MATCH_ELSE_DECODE_ERROR(
792+
unicode:characters_to_list(Bin, Encoding),
793+
String,
794+
is_list(String),
795+
{Rest,String})).
780796

781797
%% One domain name only, there must be nothing after
782798
%%
@@ -786,32 +802,55 @@ decode_domain(Bin, Buffer) ->
786802
%% Domain name -> {RestBin,Name}
787803
%%
788804
decode_name(Bin, Buffer) ->
789-
decode_name(Bin, Buffer, [], Bin, 0).
790-
791-
%% Tail advances with Rest until the first indirection is followed
792-
%% then it stays put at that Rest.
793-
decode_name(_, Buffer, _Labels, _Tail, Cnt) when Cnt > byte_size(Buffer) ->
794-
throw(?DECODE_ERROR); %% Insanity bailout - this must be a decode loop
795-
decode_name(<<0,Rest/binary>>, _Buffer, Labels, Tail, Cnt) ->
796-
%% Root domain, we have all labels for the domain name
797-
{if Cnt =/= 0 -> Tail; true -> Rest end,
805+
decode_name(Bin, Buffer, [], Bin, 0, 0).
806+
807+
decode_name(_Bin, _Buffer, _Labels, _Cont, NameLen, _PtrCnt)
808+
when NameLen >= 255 ->
809+
%% There must also be room for the root label in 255 octets
810+
%%
811+
%% One might also cap PtrCnt heuristicly at 20..50 but there is no
812+
%% support for that in RFC 1035, although almost certainly not a problem,
813+
%% and not an uncommon defensive practice.
814+
%%
815+
%% Now it is possible to craft a message that will have long
816+
%% backwards pointer chains causing high, but not catastrophically high,
817+
%% decode work.
818+
throw(?DECODE_ERROR);
819+
decode_name(<<0,Rest/binary>>, _Buffer, Labels, Cont, _NameLen, PtrCnt) ->
820+
%% Root domain; we have all labels for the domain name
821+
{decode_name_rest(Rest, Cont, PtrCnt),
798822
decode_name_labels(Labels)};
799-
decode_name(<<0:2,Len:6,Label:Len/binary,Rest/binary>>,
800-
Buffer, Labels, Tail, Cnt) ->
823+
decode_name(
824+
<<0:2,Len:6,Label:Len/binary,Rest/binary>>,
825+
Buffer, Labels, Cont, NameLen, PtrCnt) ->
801826
%% One plain label here
802-
decode_name(Rest, Buffer, [Label|Labels],
803-
if Cnt =/= 0 -> Tail; true -> Rest end,
804-
Cnt);
805-
decode_name(<<3:2,Ptr:14,Rest/binary>>, Buffer, Labels, Tail, Cnt) ->
806-
%% Indirection - reposition in buffer and recurse
827+
decode_name(
828+
Rest, Buffer, [Label|Labels], decode_name_rest(Rest, Cont, PtrCnt),
829+
NameLen + 1 + Len, PtrCnt);
830+
decode_name(
831+
<<3:2,Ptr:14,Rest/binary>>, Buffer, Labels, Cont, NameLen, PtrCnt)
832+
when
833+
%% Indirection *should* point to lower offset
834+
%% (stricter than RFC1035, but commonly used common sense),
835+
%% and *must* not point into the header.
836+
%%
837+
%% This forces a pointer loop to either end when clashing
838+
%% into the header, or get content and end on max NameLen.
839+
Ptr < byte_size(Buffer) - (byte_size(Rest) + 2),
840+
Ptr >= ?MSG_HDR_SIZE ->
841+
%% Indirection - reposition in buffer
807842
?MATCH_ELSE_DECODE_ERROR(
808843
Buffer,
809844
<<_:Ptr/binary,Bin/binary>>,
810845
decode_name(
811-
Bin, Buffer, Labels,
812-
if Cnt =/= 0 -> Tail; true -> Rest end,
813-
Cnt+2)); % size of indirection pointer
814-
decode_name(_, _, _, _, _) -> throw(?DECODE_ERROR).
846+
Bin, Buffer, Labels, decode_name_rest(Rest, Cont, PtrCnt),
847+
NameLen, PtrCnt + 1));
848+
decode_name(_Bin, _Buffer, _Labels, _Cont, _NameLen, _PtrCnt) ->
849+
throw(?DECODE_ERROR).
850+
851+
decode_name_rest(Rest, _Cont, 0) -> Rest;
852+
decode_name_rest(_Rest, Cont, _PtrCnt) -> Cont.
853+
815854

816855
%% Reverse list of labels (binaries) -> domain name (string)
817856
decode_name_labels([]) -> ".";
@@ -940,11 +979,15 @@ encode_data(Comp, Pos, ?S_NAPTR, Data) ->
940979
B0 = <<Order:16,Preference:16>>,
941980
B1 = encode_string(B0, iolist_to_binary(Flags)),
942981
B2 = encode_string(B1, iolist_to_binary(Services)),
943-
B3 = encode_string(B2, unicode:characters_to_binary(Regexp,
944-
unicode, utf8)),
945-
%% Bypass name compression (RFC 2915: section 2)
946-
{B,_} = encode_name(B3, gb_trees:empty(), Pos+byte_size(B3), Replacement),
947-
{B,Comp};
982+
case unicode:characters_to_binary(Regexp, unicode, utf8) of
983+
EncRegexp when is_binary(EncRegexp) ->
984+
B3 = encode_string(B2, EncRegexp),
985+
%% Bypass name compression (RFC 2915: section 2)
986+
{B,_} =
987+
encode_name(
988+
B3, gb_trees:empty(), Pos+byte_size(B3), Replacement),
989+
{B,Comp}
990+
end;
948991
encode_data(Comp, _, ?S_TXT, Data) -> {encode_txt(Data),Comp};
949992
encode_data(Comp, _, ?S_SPF, Data) -> {encode_txt(Data),Comp};
950993
encode_data(Comp, _, ?S_URI, Data) ->

lib/kernel/src/inet_dns_tsig.erl

Lines changed: 47 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ do_verify(Pkt,
169169
header = #dns_header{ qr = QR },
170170
arlist = ARList },
171171
TS0 = #tsig_state{ id = undefined }) ->
172-
QR = false, % ASSERT that the caller has not passed a response
172+
QR =:= false orelse throw(formerr),
173173
case ARList =/= [] andalso lists:last(ARList) of
174174
false ->
175175
{error,{notauth,badsig}};
@@ -179,14 +179,28 @@ do_verify(Pkt,
179179
original_id = OriginalId,
180180
error = ?NOERROR
181181
} = TSigRR ->
182-
{Alg,AlgSize} =
182+
{Alg,AlgSize} = % AlgSize in bytes
183183
case inet_dns:decode_algname(AlgName) of
184-
{A,S} ->
185-
{A,S};
186-
A ->
187-
{A,maps:get(size, crypto:hash_info(A))}
184+
{A,S} when is_atom(A), is_integer(S), S >= 96 ->
185+
%% {sha,96} is the smallest we know of, 96 bits
186+
lists:member(A, ?ALGS_SUPPORTED)
187+
orelse throw({notauth,badkey}),
188+
{A,S bsr 3}; % Bits -> Bytes
189+
A when is_atom(A) ->
190+
lists:member(A, ?ALGS_SUPPORTED)
191+
orelse throw({notauth,badkey}),
192+
try crypto:hash_info(A) of
193+
#{ size := S } when is_integer(S), S >= 12 ->
194+
%% sha(96) => 12 bytes
195+
{A,S};
196+
#{} ->
197+
throw({notauth,badkey})
198+
catch error : badarg ->
199+
throw({notauth,badkey})
200+
end;
201+
_ ->
202+
throw({notauth,badkey})
188203
end,
189-
lists:member(Alg, ?ALGS_SUPPORTED) orelse throw({notauth,badkey}),
190204
Key = lists:keyfind(Name, 1, TS0#tsig_state.key),
191205
Key == false andalso throw({notauth,badkey}),
192206
TS = TS0#tsig_state{
@@ -213,8 +227,10 @@ do_verify(Pkt,
213227
Response = #dns_rec{
214228
header = #dns_header{ qr = QR },
215229
arlist = ARList },
216-
TS = #tsig_state{ qr = TSQR })
217-
when QR =:= (TSQR > 0) -> % Query/response status the same in header and state
230+
TS = #tsig_state{ qr = TSQR }) ->
231+
%% Query/response status the same in header and state
232+
QR =:= (TSQR > 0) orelse throw(formerr),
233+
%%
218234
case ARList =/= [] andalso lists:last(ARList) of
219235
%% RFC8945, section 5.3.1: TSIG on TCP Connections
220236
false when TSQR == 3 -> % not 2 as we must start with a TSIG RR
@@ -257,28 +273,32 @@ do_verify(Pkt, _Response, TS = #tsig_state{ alg = {_Alg,AlgSize} }, TSigRR) ->
257273
andalso
258274
MACSize =< AlgSize,
259275
MACValid orelse throw(formerr),
260-
PktS = iolist_to_binary([
261-
<<(TS#tsig_state.id):16>>,
262-
binary:part(Pkt, {2,8}),
263-
begin
264-
<<ARC:16>> = binary:part(Pkt, {10,2}),
265-
<<(ARC - 1):16>>
276+
MACCalc =
277+
if
278+
element(1, TS#tsig_state.mac) == ?MODULE ->
279+
PktS =
280+
iolist_to_binary(
281+
[binary_part(Pkt, 0, 10),
282+
begin
283+
<<ARC:16>> = binary_part(Pkt, 10, 2),
284+
<<(ARC - 1):16>>
285+
end,
286+
binary_part(Pkt, 12, Offset-12)]),
287+
mac(PktS, TS, Error, NowSigned, OtherData);
288+
%% RFC8945, section 5.3.1: TSIG on TCP Connections
289+
true ->
290+
mac(TS, Error, NowSigned, OtherData)
266291
end,
267-
binary:part(Pkt, {12,Offset - 12})
268-
]),
269-
MACCalc = if
270-
element(1, TS#tsig_state.mac) == ?MODULE ->
271-
mac(PktS, TS, Error, NowSigned, OtherData);
272-
%% RFC8945, section 5.3.1: TSIG on TCP Connections
273-
true ->
274-
mac(TS, Error, NowSigned, OtherData)
275-
end,
292+
MACEq =
293+
MACSize == byte_size(MACCalc)
294+
andalso
295+
crypto:hash_equals(MAC, MACCalc),
276296
if
277297
%% RFC8945, section 5.2 - MUST check time after MAC
278-
MAC == MACCalc, NowSigned - Fudge < Now, NowSigned + Fudge > Now ->
298+
MACEq, NowSigned - Fudge < Now, NowSigned + Fudge > Now ->
279299
QR = if TS#tsig_state.qr == 0 -> 1; true -> 2 end,
280300
{ok,TS#tsig_state{ qr = QR, mac = {?MODULE,MAC} }};
281-
MAC == MACCalc ->
301+
MACEq ->
282302
{error,{notauth,badtime}};
283303
true ->
284304
{error,{notauth,badsig}}
@@ -313,7 +333,7 @@ macN({?MODULE,MAC}, #tsig_state{ mac = {crypto,MACState} }) ->
313333
macN(Pkt, TS = #tsig_state{ mac = {crypto,MACState} }) ->
314334
{crypto,crypto:mac_update(MACState, [
315335
<<(TS#tsig_state.id):16>>,
316-
binary:part(Pkt, {2,byte_size(Pkt) - 2})
336+
binary_part(Pkt, 2, byte_size(Pkt)-2)
317337
])}.
318338

319339
%% RFC8945, section 5.3.2: Generation of TSIG on Error Returns

lib/kernel/src/inet_res.erl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1456,8 +1456,10 @@ query_ns(S0, {Msg, Buffer}, IP, Port, Timer, Retry, I,
14561456
query_tcp(
14571457
TcpTimeout, Msg, Buffer, IP, Port, Verbose)};
14581458
{error, econnrefused} = Err ->
1459-
ok = udp_close(S),
1460-
{#sock{}, Err};
1459+
ok = udp_close(S),
1460+
{if S =:= undefined -> S;
1461+
true -> #sock{}
1462+
end, Err};
14611463
Reply -> {S, Reply}
14621464
end;
14631465
Error ->

0 commit comments

Comments
 (0)