Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions erts/emulator/nifs/unix/unix_socket_syncio.c
Original file line number Diff line number Diff line change
Expand Up @@ -4216,6 +4216,12 @@ ERL_NIF_TERM essio_recvmmsg(ErlNifEnv* env,
size_t ctrlLen;
unsigned int msgLen = recvMmsghdrs[i].msg_len;

/* With MSG_TRUNC, msg_len is the untruncated datagram length, which
* may exceed the receive buffer; only bufSz bytes were actually
* stored. Clamp so we never read past recvBufs or write past the
* result binary. */
if (msgLen > bufSz)
msgLen = bufSz;

ESOCK_ASSERT( ALLOC_BIN(bufSz, &bufs[i]) );
sys_memcpy(bufs[i].data, recvBufs + (i * bufSz), msgLen);
Expand Down
47 changes: 47 additions & 0 deletions lib/kernel/test/socket_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@
recvmmsg_large_batch_udp4/1,
sendmmsg_large_batch_udp4/1,
recvmmsg_partial_receive_udp4/1,
recvmmsg_trunc_bufsz_clamp_udp4/1,
recvmmsg_select_nowait_udp4/1,
sendmmsg_select_nowait_udp4/1,
sendmmsg_with_addresses_udp4/1,
Expand Down Expand Up @@ -386,6 +387,7 @@ batch_cases() ->
recvmmsg_large_batch_udp4,
sendmmsg_large_batch_udp4,
recvmmsg_partial_receive_udp4,
recvmmsg_trunc_bufsz_clamp_udp4,
recvmmsg_select_nowait_udp4,
sendmmsg_select_nowait_udp4,
sendmmsg_with_addresses_udp4,
Expand Down Expand Up @@ -15476,6 +15478,51 @@ recvmmsg_partial_receive_udp4(_Config) when is_list(_Config) ->
).


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Regression test: a per-message buffer smaller than the datagram, combined
%% with the 'trunc' flag.
%%
%% With MSG_TRUNC the kernel reports the *untruncated* datagram length for the
%% message, while only buffer-size bytes are actually placed in the buffer. The
%% receive path must size/copy the delivered data by the buffer size, not by the
%% reported length -- otherwise it reads/writes past the per-message buffer.
%% This case asserts the delivered data stays within the buffer; run under the
%% asan emulator an unbounded copy aborts in the sanitizer instead.
%%
recvmmsg_trunc_bufsz_clamp_udp4(_Config) when is_list(_Config) ->
?TT(?SECS(10)),
tc_try(
recvmmsg_trunc_bufsz_clamp_udp4,
fun() ->
has_support_ipv4(),
has_recvmmsg_support()
end,
fun() ->
BufSz = 16,
{ok, S1} = socket:open(inet, dgram, udp),
{ok, S2} = socket:open(inet, dgram, udp),
{ok, Addr} = inet:getaddr("localhost", inet),
ok = socket:bind(S1, #{family => inet, addr => Addr, port => 0}),
{ok, #{port := LocalPort}} = socket:sockname(S1),
ok = socket:connect(S2, #{family => inet, addr => Addr, port => LocalPort}),
%% One datagram far larger than the per-message buffer.
Big = binary:copy(<<$A>>, 2048),
ok = socket:sendmsg(S2, #{iov => [Big]}),
%% VLen=1, small BufSz, CtrlSz=0, [trunc]: the message length is
%% reported as 2048 but only BufSz bytes belong in the buffer.
{ok, [Msg]} = socket:recvmmsg(S1, 1, BufSz, 0, [trunc], infinity),

%% We should only receive BufSz bytes, not the full datagram length - verifies no overflow/copy-past-end
Data = binary:copy(<<$A>>, BufSz),
[Data] = maps:get(iov, Msg),

ok = socket:close(S1),
ok = socket:close(S2),
ok
end
).


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Test recvmmsg with nowait - verifies select path (EAGAIN handling)
%%
Expand Down
Loading