Skip to content

Commit 09783b4

Browse files
committed
Merge pull request #1483 from pguyot/w04/fix-binary-split
Fix memory corruption caused by `binary:split/2,3` These changes are made under both the "Apache 2.0" and the "GNU Lesser General Public License 2.1 or later" license terms (dual license). SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
2 parents af48881 + a44111e commit 09783b4

File tree

5 files changed

+71
-30
lines changed

5 files changed

+71
-30
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ certain VM instructions are used.
3232
- Fixed a double free when esp32 uart driver was closed, yielding an assert abort
3333
- Fixed compilation with latest debian gcc-arm-none-eabi
3434
- Fix `network:stop/0` on ESP32 so the network can be started again
35+
- Fix a memory corruption caused by `binary:split/2,3`
3536

3637
## [0.6.5] - 2024-10-15
3738

src/libAtomVM/nifs.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3121,15 +3121,19 @@ static term nif_binary_split(Context *ctx, int argc, term argv[])
31213121
size_t num_segments = 1;
31223122
const char *temp_bin_data = bin_data;
31233123
int temp_bin_size = bin_size;
3124+
size_t heap_size = 0;
31243125
do {
31253126
const char *found = (const char *) memmem(temp_bin_data, temp_bin_size, pattern_data, pattern_size);
31263127
if (!found) break;
31273128
num_segments++;
3129+
heap_size += CONS_SIZE + term_sub_binary_heap_size(argv[0], found - temp_bin_data);
31283130
int next_search_offset = found - temp_bin_data + pattern_size;
31293131
temp_bin_data += next_search_offset;
31303132
temp_bin_size -= next_search_offset;
31313133
} while (global && temp_bin_size >= pattern_size);
31323134

3135+
heap_size += CONS_SIZE + term_sub_binary_heap_size(argv[0], temp_bin_size);
3136+
31333137
term result_list = term_nil();
31343138

31353139
if (num_segments == 1) {
@@ -3142,7 +3146,7 @@ static term nif_binary_split(Context *ctx, int argc, term argv[])
31423146
}
31433147

31443148
// binary:split/2,3 always return sub binaries, except when copied binaries are as small as sub-binaries.
3145-
if (UNLIKELY(memory_ensure_free_with_roots(ctx, LIST_SIZE(num_segments, TERM_BOXED_SUB_BINARY_SIZE), 2, argv, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
3149+
if (UNLIKELY(memory_ensure_free_with_roots(ctx, heap_size, 2, argv, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
31463150
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
31473151
}
31483152

src/libAtomVM/term.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1068,7 +1068,7 @@ static inline term term_from_literal_binary(const void *data, size_t size, Heap
10681068
*/
10691069
static inline size_t term_sub_binary_heap_size(term binary, size_t len)
10701070
{
1071-
if (term_is_refc_binary(binary) && len >= SUB_BINARY_MIN) {
1071+
if ((term_is_refc_binary(binary) || term_is_sub_binary(binary)) && len >= SUB_BINARY_MIN) {
10721072
return TERM_BOXED_SUB_BINARY_SIZE;
10731073
} else {
10741074
return term_binary_heap_size(len);

tests/erlang_tests/test_binary_split.erl

Lines changed: 63 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -20,63 +20,99 @@
2020

2121
-module(test_binary_split).
2222

23-
-export([start/0, split_compare/3, split_compare/2, compare_bin/2, fail_split/1]).
23+
-export([start/0, split_compare/3, split_compare/2, compare_bin/2, fail_split/1, id/1]).
2424

2525
start() ->
26-
split_compare(<<"Hello:World">>, <<"Hello">>, <<"World">>) +
27-
split_compare(<<"Hello:::World:">>, <<"Hello">>, <<"::World:">>) +
28-
split_compare(<<"Test:">>, <<"Test">>, <<>>) +
29-
split_compare(<<":">>, <<>>, <<>>) +
30-
split_compare(<<>>, <<>>) +
31-
split_compare(<<"Test">>, <<>>) +
32-
split_compare2(<<"Test">>, <<>>) +
33-
split_compare2(<<"helloSEPARATORworld">>, <<"hello">>, <<"world">>) +
34-
fail_split(<<>>) +
35-
fail_split({1, 2}) +
36-
fail_split2({1, 2}).
26+
ok = split_compare(<<"Hello:World">>, <<"Hello">>, <<"World">>),
27+
ok = split_compare(<<"Hello:::World:">>, <<"Hello">>, <<"::World:">>),
28+
ok = split_compare(<<"Test:">>, <<"Test">>, <<>>),
29+
ok = split_compare(<<":">>, <<>>, <<>>),
30+
ok = split_compare(<<>>, <<>>),
31+
ok = split_compare(<<"Test">>, <<>>),
32+
ok = split_compare2(<<"Test">>, <<>>),
33+
ok = split_compare2(<<"helloSEPARATORworld">>, <<"hello">>, <<"world">>),
34+
ok = fail_split(<<>>),
35+
ok = fail_split({1, 2}),
36+
ok = fail_split2({1, 2}),
37+
case erlang:system_info(machine) of
38+
"BEAM" -> ok;
39+
"ATOM" -> ok = memory_allocation_split()
40+
end,
41+
0.
3742

3843
split_compare(Bin, Part1) ->
3944
[A] = binary:split(Bin, <<":">>),
4045
compare_bin(Part1, A).
4146

4247
split_compare(Bin, Part1, Part2) ->
4348
[A, B] = binary:split(Bin, <<":">>),
44-
compare_bin(Part1, A) + compare_bin(B, Part2).
49+
ok = compare_bin(Part1, A),
50+
ok = compare_bin(B, Part2).
4551

4652
split_compare2(Bin, Part1) ->
4753
[A] = binary:split(Bin, <<"SEPARATOR">>),
4854
compare_bin(Part1, A).
4955

5056
split_compare2(Bin, Part1, Part2) ->
5157
[A, B] = binary:split(Bin, <<"SEPARATOR">>),
52-
compare_bin(Part1, A) + compare_bin(B, Part2).
58+
ok = compare_bin(Part1, A),
59+
ok = compare_bin(B, Part2).
5360

5461
compare_bin(Bin1, Bin2) ->
5562
compare_bin(Bin1, Bin2, byte_size(Bin1) - 1).
5663

5764
compare_bin(_Bin1, _Bin2, -1) ->
58-
1;
65+
ok;
5966
compare_bin(Bin1, Bin2, Index) ->
6067
B1 = binary:at(Bin1, Index),
61-
case binary:at(Bin2, Index) of
62-
B1 ->
63-
compare_bin(Bin1, Bin2, Index - 1);
64-
_Any ->
65-
0
66-
end.
68+
B1 = binary:at(Bin2, Index),
69+
compare_bin(Bin1, Bin2, Index - 1).
6770

6871
fail_split(Separator) ->
6972
try binary:split(<<"TESTBIN">>, Separator) of
70-
_Any -> 2000
73+
_Any -> {unexpected, _Any}
7174
catch
72-
error:badarg -> 1;
73-
_:_ -> 4000
75+
error:badarg -> ok;
76+
T:V -> {unexpected, {T, V}}
7477
end.
7578

7679
fail_split2(Bin) ->
7780
try binary:split(Bin, <<"TESTSEPARATOR">>) of
78-
_Any -> 2000
81+
_Any -> {unxpected, _Any}
7982
catch
80-
error:badarg -> 1;
81-
_:_ -> 4000
83+
error:badarg -> ok;
84+
T:V -> {unxpected, {T, V}}
8285
end.
86+
87+
memory_allocation_split() ->
88+
Parent = self(),
89+
Hostname = <<"atomvm">>,
90+
{Pid, MonitorRef} = spawn_opt(
91+
fun() ->
92+
% Carefully designed lists to generate a crash on unix 64 bits
93+
% This binary is 63 bytes long, so it's stored on heap on 64 bits
94+
% binary:split should allocate sufficient bytes as subbinaries
95+
% have to be on heap as well
96+
HeapBin = list_to_binary([
97+
id(Hostname), <<"@atomvms3.object.stream.atomvms3.object.stream.atomvms3.o">>
98+
]),
99+
List1 = binary:split(HeapBin, <<"@">>, [global]),
100+
Parent ! {self(), List1}
101+
end,
102+
[link, monitor, {atomvm_heap_growth, minimum}]
103+
),
104+
ok =
105+
receive
106+
{Pid, List1} ->
107+
2 = length(List1),
108+
ok
109+
after 5000 -> timeout
110+
end,
111+
normal =
112+
receive
113+
{'DOWN', MonitorRef, process, Pid, Reason} -> Reason
114+
after 5000 -> timeout
115+
end,
116+
ok.
117+
118+
id(X) -> X.

tests/test.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ struct Test tests[] = {
318318
TEST_CASE(test_unicode),
319319

320320
TEST_CASE_EXPECTED(test_binary_part, 12),
321-
TEST_CASE_EXPECTED(test_binary_split, 16),
321+
TEST_CASE(test_binary_split),
322322

323323
TEST_CASE_COND(plusone, 134217728, LONG_MAX != 9223372036854775807),
324324

0 commit comments

Comments
 (0)