Skip to content

Commit 71c3d00

Browse files
authored
Always free allocaed memory in mmunmap. (#21363)
Without this change we were returning early from `__syscall_munmap` if `_munmap_js` failed. This could happen if `fd` was no longer a valid open file descriptor. However, even in this case we still want to go ahead and free any allocated memory. Fixes: #21360
1 parent 7cdfa8d commit 71c3d00

8 files changed

+305
-289
lines changed

src/library_fs.js

-1
Original file line numberDiff line numberDiff line change
@@ -1276,7 +1276,6 @@ FS.staticInit();` +
12761276
}
12771277
return stream.stream_ops.msync(stream, buffer, offset, length, mmapFlags);
12781278
},
1279-
munmap: (stream) => 0,
12801279
ioctl(stream, cmd, arg) {
12811280
if (!stream.stream_ops.ioctl) {
12821281
throw new FS.ErrnoError({{{ cDefs.ENOTTY }}});

src/library_noderawfs.js

-3
Original file line numberDiff line numberDiff line change
@@ -193,9 +193,6 @@ addToLibrary({
193193
// should we check if bytesWritten and length are the same?
194194
return 0;
195195
},
196-
munmap() {
197-
return 0;
198-
},
199196
ioctl() {
200197
throw new FS.ErrnoError({{{ cDefs.ENOTTY }}});
201198
}

src/library_syscall.js

-3
Original file line numberDiff line numberDiff line change
@@ -159,13 +159,10 @@ var SyscallsLibrary = {
159159
_munmap_js__i53abi: true,
160160
_munmap_js: (addr, len, prot, flags, fd, offset) => {
161161
#if FILESYSTEM && SYSCALLS_REQUIRE_FILESYSTEM
162-
if (isNaN(offset)) return {{{ cDefs.EOVERFLOW }}};
163162
var stream = SYSCALLS.getStreamFromFD(fd);
164163
if (prot & {{{ cDefs.PROT_WRITE }}}) {
165164
SYSCALLS.doMsync(addr, stream, len, flags, offset);
166165
}
167-
FS.munmap(stream);
168-
// implicitly return 0
169166
#endif
170167
},
171168

system/lib/libc/emscripten_mmap.c

+1-4
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,7 @@ int __syscall_munmap(intptr_t addr, size_t length) {
7373
UNLOCK(lock);
7474

7575
if (!(map->flags & MAP_ANONYMOUS)) {
76-
int rtn = _munmap_js(addr, length, map->prot, map->flags, map->fd, map->offset);
77-
if (rtn) {
78-
return rtn;
79-
}
76+
_munmap_js(addr, length, map->prot, map->flags, map->fd, map->offset);
8077
}
8178

8279
// Release the memory.

test/other/test_mmap_and_munmap.c

+298
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
/*
2+
* Copyright 2019 The Emscripten Authors. All rights reserved.
3+
* Emscripten is available under two separate licenses, the MIT license and the
4+
* University of Illinois/NCSA Open Source License. Both these licenses can be
5+
* found in the LICENSE file.
6+
*/
7+
8+
#include <assert.h>
9+
#include <errno.h>
10+
#include <limits.h>
11+
#include <stdint.h>
12+
#include <stdio.h>
13+
#include <string.h>
14+
#include <sys/mman.h>
15+
#include <malloc.h>
16+
#include <unistd.h>
17+
18+
#define FNAME_RO "data_ro.dat"
19+
#define FNAME_RW "data_rw.dat"
20+
21+
/*
22+
* Each test will return 0 as success (no error) and error code in case of failure:
23+
* - > 0 it is errno value
24+
* - < 0 test specific failure (not related to any syscall)
25+
*/
26+
27+
#define ASSERT(cond, msg) do { \
28+
if (!(cond)) { \
29+
printf("%s:%03d FAILED '%s' - %s errno: %d\n", \
30+
__func__, __LINE__, #cond, msg, errno); \
31+
return (errno != 0 ? errno : -1); \
32+
} \
33+
} while (0)
34+
35+
#define TEST_START() printf("%s - START\n", __func__)
36+
#define TEST_PASS() do { \
37+
printf("%s - PASSED\n", __func__); \
38+
return 0; \
39+
} while (0)
40+
41+
const char file_data[] =
42+
"Copyright 2019 The Emscripten Authors. All rights reserved.\n"
43+
"Emscripten is available under two separate licenses, the MIT license and the\n"
44+
"University of Illinois/NCSA Open Source License. Both these licenses can be\n"
45+
"found in the LICENSE file.\n";
46+
47+
FILE *f_ro = NULL;
48+
FILE *f_rw = NULL;
49+
50+
// Length of the file data excluding trailing '\0'. Equivalent to strlen(file_data) .
51+
size_t file_len() {
52+
return sizeof(file_data) - 1;
53+
}
54+
55+
int test_mmap_read() {
56+
TEST_START();
57+
errno = 0;
58+
59+
char *m = (char *)mmap(NULL, file_len(), PROT_READ, MAP_PRIVATE, fileno(f_ro), 0);
60+
ASSERT(m != MAP_FAILED, "Failed to mmap file");
61+
for (size_t i = 0; i < file_len(); ++i) {
62+
ASSERT(m[i] == file_data[i], "Wrong file data mmaped");
63+
}
64+
ASSERT(munmap(m, file_len()) == 0, "Failed to unmap allocated pages");
65+
TEST_PASS();
66+
}
67+
68+
int test_mmap_write() {
69+
TEST_START();
70+
errno = 0;
71+
72+
char *m = (char *)mmap(NULL, file_len(), PROT_READ | PROT_WRITE, MAP_SHARED, fileno(f_rw), 0);
73+
ASSERT(m != MAP_FAILED, "Failed to mmap file");
74+
// Reverse the data in the mapped file in memory, which should be written
75+
// out.
76+
for (size_t i = 0; i < file_len(); ++i) {
77+
size_t k = file_len() - i - 1;
78+
m[k] = file_data[i];
79+
}
80+
// Write to a byte past the end of the file. mmap will allocate a multiple
81+
// of the page size, which is bigger than our small file, and it will zero
82+
// that out. So it is ok for us to write there, but those changes should
83+
// not be saved anywhere.
84+
ASSERT(m[file_len()] == 0, "No zero past file contents");
85+
m[file_len()] = 42;
86+
ASSERT(munmap(m, file_len()) == 0, "Failed to unmap allocated pages");
87+
88+
// mmap it again, where we should see the reversed data that was written.
89+
m = (char *)mmap(NULL, file_len(), PROT_READ, MAP_PRIVATE, fileno(f_rw), 0);
90+
ASSERT(m != MAP_FAILED, "Failed to mmap file");
91+
for (size_t i = 0; i < file_len(); ++i) {
92+
size_t k = file_len() - i - 1;
93+
ASSERT(m[k] == file_data[i], "Wrong file data written or mapped");
94+
}
95+
ASSERT(m[file_len()] == 0, "No zero past file contents");
96+
ASSERT(munmap(m, file_len()) == 0, "Failed to unmap allocated pages");
97+
98+
TEST_PASS();
99+
}
100+
101+
int test_mmap_write_private() {
102+
TEST_START();
103+
errno = 0;
104+
105+
/*
106+
* POSIX spec (http://pubs.opengroup.org/onlinepubs/9699919799/) for mmap
107+
* says that it should be possible to set PROT_WRITE flag for file opened
108+
* in RO mode if we use MAP_PRIVATE flag.
109+
*/
110+
char *m = (char *)mmap(NULL, file_len(), PROT_READ | PROT_WRITE, MAP_PRIVATE, fileno(f_ro), 0);
111+
ASSERT(m != MAP_FAILED, "Failed to mmap file");
112+
for (size_t i = 0; i < file_len(); ++i) {
113+
size_t k = file_len() - i;
114+
m[k] = file_data[i];
115+
}
116+
ASSERT(munmap(m, file_len()) == 0, "Failed to unmap allocated pages");
117+
118+
/*
119+
* It is undefined however, if subsequent maps in the same process will or won't
120+
* see data written by such mmap.
121+
*/
122+
m = (char *)mmap(NULL, file_len(), PROT_READ, MAP_PRIVATE, fileno(f_ro), 0);
123+
ASSERT(m != MAP_FAILED, "Failed to mmap file");
124+
for (size_t i = 0; i < file_len(); ++i) {
125+
size_t k = file_len() - i;
126+
ASSERT(m[k] == file_data[i] || m[i] == file_data[i],
127+
"Wrong file data written or mapped");
128+
}
129+
ASSERT(munmap(m, file_len()) == 0, "Failed to unmap allocated pages");
130+
131+
TEST_PASS();
132+
}
133+
134+
int test_mmap_write_to_ro_file() {
135+
TEST_START();
136+
errno = 0;
137+
138+
char *m = (char *)mmap(NULL, file_len(), PROT_READ | PROT_WRITE, MAP_SHARED, fileno(f_ro), 0);
139+
ASSERT(m == MAP_FAILED && errno == EACCES,
140+
"Expected EACCES when requesting writing to file opened in read-only mode");
141+
TEST_PASS();
142+
}
143+
144+
int test_mmap_anon() {
145+
TEST_START();
146+
errno = 0;
147+
148+
void *m = (char *)mmap(NULL, file_len(), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
149+
ASSERT(m != MAP_FAILED, "Failed to mmap pages");
150+
ASSERT(munmap(m, file_len()) == 0, "Failed to unmmap allocated pages");
151+
TEST_PASS();
152+
}
153+
154+
int test_mmap_fixed() {
155+
TEST_START();
156+
errno = 0;
157+
size_t page_size = sysconf(_SC_PAGE_SIZE);
158+
159+
char *m = (char *)mmap(NULL, file_len(), PROT_READ, MAP_PRIVATE, fileno(f_ro), 0);
160+
ASSERT(m != MAP_FAILED, "Failed to mmap file");
161+
char *invalid_addr = m;
162+
if (((uintptr_t)m) % page_size == 0) {
163+
invalid_addr++;
164+
}
165+
166+
char *m2 = (char *)mmap(invalid_addr, file_len(), PROT_READ, MAP_FIXED, fileno(f_ro), 0);
167+
ASSERT(m2 == MAP_FAILED && errno == EINVAL, "Expected EINVAL for invalid addr");
168+
169+
size_t invalid_offset = 1;
170+
char *m3 = (char *)mmap(m, file_len(), PROT_READ, MAP_FIXED, fileno(f_ro), invalid_offset);
171+
ASSERT(m3 == MAP_FAILED && errno == EINVAL, "Expected EINVAL for invalid offset");
172+
173+
ASSERT(munmap(m, file_len()) == 0, "Failed to unmmap allocated pages");
174+
TEST_PASS();
175+
}
176+
177+
int test_mmap_wrong_fd() {
178+
TEST_START();
179+
errno = 0;
180+
181+
char *m = (char *)mmap(NULL, file_len(), PROT_READ, MAP_PRIVATE, -1, 0);
182+
ASSERT(m == MAP_FAILED && errno == EBADF, "Expected EBADF error");
183+
TEST_PASS();
184+
}
185+
186+
int test_unmap_wrong_addr() {
187+
TEST_START();
188+
errno = 0;
189+
190+
ASSERT(munmap((void*)0xdeadbeef, file_len()) == -1 && errno == EINVAL,
191+
"Expected EINVAL, as munmap should fail for wrong addr argument");
192+
TEST_PASS();
193+
}
194+
195+
int test_unmap_zero_len() {
196+
TEST_START();
197+
errno = 0;
198+
199+
char *m = (char *)mmap(NULL, file_len(), PROT_READ, MAP_PRIVATE, fileno(f_ro), 0);
200+
ASSERT(m != MAP_FAILED, "Failed to mmap file");
201+
ASSERT(munmap(m, 0) == -1 && errno == EINVAL,
202+
"Expected EINVAL, as munmap should fail when len argument is 0");
203+
ASSERT(munmap(m, file_len()) == 0, "Failed to unmap allocated pages");
204+
TEST_PASS();
205+
}
206+
207+
static int get_heap_usage() {
208+
struct mallinfo info = mallinfo();
209+
return info.uordblks;
210+
}
211+
212+
int test_unmap_after_close() {
213+
TEST_START();
214+
errno = 0;
215+
216+
int heap_start = get_heap_usage();
217+
218+
for (int i = 0; i < 10; i++) {
219+
FILE *f = fopen(FNAME_RO, "r");
220+
char *m = (char *)mmap(NULL, file_len(), PROT_READ, MAP_PRIVATE, fileno(f), 0);
221+
ASSERT(m != MAP_FAILED, "Failed to mmap file");
222+
223+
fclose(f);
224+
225+
// Call munmap after closing the file
226+
ASSERT(munmap(m, file_len()) == 0, "Failed to unmap allocated pages");
227+
}
228+
229+
int heap_end = get_heap_usage();
230+
ASSERT(heap_start == heap_end, "Memory leak during mmap/munmap");
231+
TEST_PASS();
232+
}
233+
234+
typedef int (*test_fn)();
235+
236+
int set_up() {
237+
FILE *f = fopen(FNAME_RO, "w");
238+
ASSERT(f != NULL, "Failed to open file " FNAME_RO " for writing");
239+
fprintf(f, "%s", file_data);
240+
fclose(f);
241+
242+
f = fopen(FNAME_RW, "w");
243+
ASSERT(f != NULL, "Failed to open file " FNAME_RW " for writing");
244+
fprintf(f, "%s", file_data);
245+
fclose(f);
246+
247+
f_ro = fopen(FNAME_RO, "r");
248+
ASSERT(f_ro != NULL, "Failed to open file " FNAME_RO " for reading");
249+
f_rw = fopen(FNAME_RW, "r+");
250+
ASSERT(f_rw != NULL, "Failed to open file " FNAME_RW " for reading and writing");
251+
252+
return 0;
253+
}
254+
255+
void tear_down() {
256+
if (f_ro != NULL) {
257+
fclose(f_ro);
258+
f_ro = NULL;
259+
}
260+
261+
if (f_rw != NULL) {
262+
fclose(f_rw);
263+
f_rw = NULL;
264+
}
265+
}
266+
267+
int main() {
268+
int failures = 0;
269+
test_fn tests[] = {
270+
test_mmap_read,
271+
test_mmap_write,
272+
test_mmap_write_private,
273+
test_mmap_write_to_ro_file,
274+
test_mmap_anon,
275+
test_mmap_fixed,
276+
test_mmap_wrong_fd,
277+
test_unmap_wrong_addr,
278+
test_unmap_zero_len,
279+
test_unmap_after_close,
280+
NULL
281+
};
282+
283+
int tests_run = 0;
284+
while (tests[tests_run] != NULL) {
285+
if (set_up() != 0) {
286+
printf("Failed to set_up environment for TC\n");
287+
++failures;
288+
} else {
289+
failures += (tests[tests_run]() != 0 ? 1 : 0);
290+
}
291+
292+
tear_down();
293+
++tests_run;
294+
}
295+
296+
printf("tests_run: %d failures: %d\n", tests_run, failures);
297+
return failures;
298+
}

0 commit comments

Comments
 (0)