This is an automated email from the ASF dual-hosted git repository.
Jens-G pushed a commit to branch release/0.25.0
in repository https://gitbox.apache.org/repos/asf/thrift.git
The following commit(s) were added to refs/heads/release/0.25.0 by this push:
new 27e8a425f THRIFT-6367: Cap the message name in the Erlang binary and
compact protocols Client: erl
27e8a425f is described below
commit 27e8a425ffb498e190df3a12e239326bf5ba9ed6
Author: Jens Geyer <[email protected]>
AuthorDate: Thu Sep 24 00:58:25 2026 +0200
THRIFT-6367: Cap the message name in the Erlang binary and compact protocols
Client: erl
message_begin of thrift_binary_protocol and thrift_compact_protocol now
reads the declared length of the name first and refuses a name longer than
?MAX_MESSAGE_NAME_SIZE (4096 bytes) before reading it, with
{error, {message_name_exceeds_maximum, 4096}}. A name the processor can
dispatch is at most 255 characters for the function, plus a service name
and a ':' for a multiplexed service.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
---
lib/erl/include/thrift_constants.hrl | 6 +++---
lib/erl/src/thrift_binary_protocol.erl | 22 ++++++++++++++++++----
lib/erl/src/thrift_compact_protocol.erl | 18 +++++++++++++-----
lib/erl/test/test_thrift_max_message_size.erl | 19 +++++++++++++++++++
4 files changed, 53 insertions(+), 12 deletions(-)
diff --git a/lib/erl/include/thrift_constants.hrl
b/lib/erl/include/thrift_constants.hrl
index 5339131f5..d5a83c4dd 100644
--- a/lib/erl/include/thrift_constants.hrl
+++ b/lib/erl/include/thrift_constants.hrl
@@ -82,7 +82,7 @@
%% its max_message_size option.
-define(DEFAULT_MAX_MESSAGE_SIZE, (100 * 1024 * 1024)).
-%% The longest message name thrift_json_protocol will read. A name the
-%% processor can dispatch is a function name, an atom of at most 255
-%% characters, after a service name and a ':' for a multiplexed service.
+%% The longest message name the binary, compact and JSON protocols will read.
+%% A name the processor can dispatch is a function name, an atom of at most
+%% 255 characters, after a service name and a ':' for a multiplexed service.
-define(MAX_MESSAGE_NAME_SIZE, 4096).
diff --git a/lib/erl/src/thrift_binary_protocol.erl
b/lib/erl/src/thrift_binary_protocol.erl
index b1cf57f6b..37c1cd0cd 100644
--- a/lib/erl/src/thrift_binary_protocol.erl
+++ b/lib/erl/src/thrift_binary_protocol.erl
@@ -186,9 +186,14 @@ read(This0 = #binary_protocol{max_message_size = Max},
message_begin) ->
case Initial of
{ok, Sz} when Sz band ?VERSION_MASK =:= ?VERSION_1 ->
%% we're at version 1
- case read(This1, string) of
- {This2, {ok, Name}} ->
- read_message_begin(This2, Name, Sz band ?TYPE_MASK);
+ case read(This1, i32) of
+ {This2, {ok, NameSz}} ->
+ case read_name(This2, NameSz) of
+ {This3, {ok, Name}} ->
+ read_message_begin(This3, Name, Sz band
?TYPE_MASK);
+ {This3, Error} ->
+ {This3, Error}
+ end;
{This2, Error} ->
{This2, Error}
end;
@@ -200,7 +205,7 @@ read(This0 = #binary_protocol{max_message_size = Max},
message_begin) ->
{This1, {error, no_binary_protocol_version}};
{ok, Sz} when This1#binary_protocol.strict_read =:= false ->
%% strict_read is false, so just read the old way
- case read_data(This1, Sz) of
+ case read_name(This1, Sz) of
{This2, {ok, Name}} ->
case read(This2, byte) of
{This3, {ok, Type}} -> read_message_begin(This3, Name,
Type);
@@ -320,6 +325,15 @@ read(This0, string) ->
if Sz < 0 -> error({protocol_error, negative_size}); true -> ok end,
read_data(This1, Sz).
+%% The name of a message, refused before it is read if it is longer than
+%% ?MAX_MESSAGE_NAME_SIZE.
+read_name(_This, Sz) when Sz < 0 ->
+ error({protocol_error, negative_size});
+read_name(This, Sz) when Sz > ?MAX_MESSAGE_NAME_SIZE ->
+ {This, {error, {message_name_exceeds_maximum, ?MAX_MESSAGE_NAME_SIZE}}};
+read_name(This, Sz) ->
+ read_data(This, Sz).
+
%% The sequence id that ends a message header, once its name and type are read.
read_message_begin(This0, Name, Type) ->
case read(This0, i32) of
diff --git a/lib/erl/src/thrift_compact_protocol.erl
b/lib/erl/src/thrift_compact_protocol.erl
index 70464e1f6..2f3886e0d 100644
--- a/lib/erl/src/thrift_compact_protocol.erl
+++ b/lib/erl/src/thrift_compact_protocol.erl
@@ -271,15 +271,16 @@ read(This0 = #t_compact{max_message_size = Max},
message_begin) ->
{This2, {ok, VerAndType}} = read(This1, ubyte),
?VERSION_1 = VerAndType band ?VERSION_MASK,
{This3, {ok, SeqId}} = read(This2, ui32),
- case read(This3, string) of
- {This4, {ok, Name}} ->
- {This4, #protocol_message_begin{
+ {This4, {ok, NameSz}} = read(This3, ui32),
+ case read_name(This4, NameSz) of
+ {This5, {ok, Name}} ->
+ {This5, #protocol_message_begin{
name = binary_to_list(Name),
type = (VerAndType bsr ?TYPE_SHIFT_AMOUNT) band ?TYPE_BITS,
seqid = SeqId
}};
- {This4, Error} ->
- {This4, Error}
+ {This5, Error} ->
+ {This5, Error}
end;
read(This, message_end) ->
{This#t_compact{message_bytes_left = undefined}, ok};
@@ -401,6 +402,13 @@ read(This0, string) ->
{This1, {ok, Sz}} = read(This0, ui32),
read_data(This1, Sz).
+%% The name of a message, refused before it is read if it is longer than
+%% ?MAX_MESSAGE_NAME_SIZE.
+read_name(This, Sz) when Sz > ?MAX_MESSAGE_NAME_SIZE ->
+ {This, {error, {message_name_exceeds_maximum, ?MAX_MESSAGE_NAME_SIZE}}};
+read_name(This, Sz) ->
+ read_data(This, Sz).
+
-spec read_data(#t_compact{}, non_neg_integer()) ->
{#t_compact{}, {ok, binary()} | {error, _Reason}}.
read_data(This, 0) ->
diff --git a/lib/erl/test/test_thrift_max_message_size.erl
b/lib/erl/test/test_thrift_max_message_size.erl
index d9364ab02..176776716 100644
--- a/lib/erl/test/test_thrift_max_message_size.erl
+++ b/lib/erl/test/test_thrift_max_message_size.erl
@@ -139,6 +139,25 @@ name_test_() ->
|| {Name, Module, Base} <- protocols()
].
+%% A message name up to ?MAX_MESSAGE_NAME_SIZE bytes is read. A longer one is
+%% refused by message_begin before its bytes are taken from the transport.
+name_length_test_() ->
+ [
+ {Name,
+ ?_test(begin
+ AtMax = lists:duplicate(?MAX_MESSAGE_NAME_SIZE, $n),
+ Ok = message(Module, Base, AtMax, <<"x">>),
+ ?assertMatch({{ok, AtMax, _}, _, _}, read_call(new(Module, Ok,
Base), Ok)),
+ Long = message(Module, Base, [$n | AtMax], <<"x">>),
+ {Protocol, Result} = thrift_protocol:read(new(Module, Long,
Base), message_begin),
+ ?assertEqual(
+ {error, {message_name_exceeds_maximum,
?MAX_MESSAGE_NAME_SIZE}}, Result
+ ),
+ ?assert(byte_size(Long) - iolist_size(buffered(Protocol)) =< 8)
+ end)}
+ || {Name, Module, Base} <- protocols()
+ ].
+
%% Each message has the whole maximum to itself.
one_message_after_another_test_() ->
[