1 /*
2 * Copyright (c) 2013-2019 Huawei Technologies Co., Ltd. All rights reserved.
3 * Copyright (c) 2020-2021 Huawei Device Co., Ltd. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without modification,
6 * are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this list of
9 * conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice, this list
12 * of conditions and the following disclaimer in the documentation and/or other materials
13 * provided with the distribution.
14 *
15 * 3. Neither the name of the copyright holder nor the names of its contributors may be used
16 * to endorse or promote products derived from this software without specific prior written
17 * permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
23 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
26 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
27 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
28 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
29 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 /**
33 * @defgroup los_vm_syscall vm syscall definition
34 * @ingroup kernel
35 */
36
37 #include "los_typedef.h"
38 #include "los_vm_syscall.h"
39 #include "los_vm_common.h"
40 #include "los_rbtree.h"
41 #include "los_vm_map.h"
42 #include "los_vm_dump.h"
43 #include "los_vm_lock.h"
44 #include "los_vm_filemap.h"
45 #include "los_process_pri.h"
46
47
48 #ifdef LOSCFG_KERNEL_VM
49
OsCheckMMapParams(VADDR_T * vaddr,unsigned long flags,size_t len,unsigned long pgoff)50 STATUS_T OsCheckMMapParams(VADDR_T *vaddr, unsigned long flags, size_t len, unsigned long pgoff)
51 {
52 if ((len == 0) || (len > USER_ASPACE_SIZE)) {
53 return -EINVAL;
54 }
55 if (len > OsCurrProcessGet()->vmSpace->mapSize) {
56 return -ENOMEM;
57 }
58
59 if (((flags & MAP_FIXED) == 0) && ((flags & MAP_FIXED_NOREPLACE) == 0)) {
60 *vaddr = ROUNDUP(*vaddr, PAGE_SIZE);
61 if ((*vaddr != 0) && (!LOS_IsUserAddressRange(*vaddr, len))) {
62 *vaddr = 0;
63 }
64 } else if ((!LOS_IsUserAddressRange(*vaddr, len)) || (!IS_ALIGNED(*vaddr, PAGE_SIZE))) {
65 return -EINVAL;
66 }
67
68 if ((flags & MAP_SUPPORT_MASK) == 0) {
69 return -EINVAL;
70 }
71 if (((flags & MAP_SHARED_PRIVATE) == 0) || ((flags & MAP_SHARED_PRIVATE) == MAP_SHARED_PRIVATE)) {
72 return -EINVAL;
73 }
74
75 if (((len >> PAGE_SHIFT) + pgoff) < pgoff) {
76 return -EINVAL;
77 }
78
79 return LOS_OK;
80 }
81
OsNamedMmapingPermCheck(struct file * filep,unsigned long flags,unsigned prot)82 STATUS_T OsNamedMmapingPermCheck(struct file *filep, unsigned long flags, unsigned prot)
83 {
84 if (!((unsigned int)filep->f_oflags & O_RDWR) && (((unsigned int)filep->f_oflags & O_ACCMODE) ^ O_RDONLY)) {
85 return -EACCES;
86 }
87 if (flags & MAP_SHARED) {
88 if (((unsigned int)filep->f_oflags & O_APPEND) && (prot & PROT_WRITE)) {
89 return -EACCES;
90 }
91 if ((prot & PROT_WRITE) && !((unsigned int)filep->f_oflags & O_RDWR)) {
92 return -EACCES;
93 }
94 }
95
96 return LOS_OK;
97 }
98
OsAnonMMap(LosVmMapRegion * region)99 STATUS_T OsAnonMMap(LosVmMapRegion *region)
100 {
101 LOS_SetRegionTypeAnon(region);
102 return LOS_OK;
103 }
104
LOS_MMap(VADDR_T vaddr,size_t len,unsigned prot,unsigned long flags,int fd,unsigned long pgoff)105 VADDR_T LOS_MMap(VADDR_T vaddr, size_t len, unsigned prot, unsigned long flags, int fd, unsigned long pgoff)
106 {
107 STATUS_T status;
108 VADDR_T resultVaddr;
109 UINT32 regionFlags;
110 LosVmMapRegion *newRegion = NULL;
111 struct file *filep = NULL;
112 LosVmSpace *vmSpace = OsCurrProcessGet()->vmSpace;
113
114 len = ROUNDUP(len, PAGE_SIZE);
115 STATUS_T checkRst = OsCheckMMapParams(&vaddr, flags, len, pgoff);
116 if (checkRst != LOS_OK) {
117 return checkRst;
118 }
119
120 if (LOS_IsNamedMapping(flags)) {
121 status = fs_getfilep(fd, &filep);
122 if (status < 0) {
123 return -EBADF;
124 }
125
126 status = OsNamedMmapingPermCheck(filep, flags, prot);
127 if (status < 0) {
128 return status;
129 }
130 }
131
132 (VOID)LOS_MuxAcquire(&vmSpace->regionMux);
133 /* user mode calls mmap to release heap physical memory without releasing heap virtual space */
134 status = OsUserHeapFree(vmSpace, vaddr, len);
135 if (status == LOS_OK) {
136 resultVaddr = vaddr;
137 goto MMAP_DONE;
138 }
139
140 regionFlags = OsCvtProtFlagsToRegionFlags(prot, flags);
141 newRegion = LOS_RegionAlloc(vmSpace, vaddr, len, regionFlags, pgoff);
142 if (newRegion == NULL) {
143 resultVaddr = (VADDR_T)-ENOMEM;
144 goto MMAP_DONE;
145 }
146 newRegion->regionFlags |= VM_MAP_REGION_FLAG_MMAP;
147 resultVaddr = newRegion->range.base;
148
149 if (LOS_IsNamedMapping(flags)) {
150 status = OsNamedMMap(filep, newRegion);
151 } else {
152 status = OsAnonMMap(newRegion);
153 }
154
155 if (status != LOS_OK) {
156 LOS_RbDelNode(&vmSpace->regionRbTree, &newRegion->rbNode);
157 LOS_RegionFree(vmSpace, newRegion);
158 resultVaddr = (VADDR_T)-ENOMEM;
159 goto MMAP_DONE;
160 }
161
162 MMAP_DONE:
163 (VOID)LOS_MuxRelease(&vmSpace->regionMux);
164 return resultVaddr;
165 }
166
LOS_UnMMap(VADDR_T addr,size_t size)167 STATUS_T LOS_UnMMap(VADDR_T addr, size_t size)
168 {
169 if ((addr <= 0) || (size == 0)) {
170 return -EINVAL;
171 }
172
173 return OsUnMMap(OsCurrProcessGet()->vmSpace, addr, size);
174 }
175
OsProtMprotectPermCheck(unsigned long prot,LosVmMapRegion * region)176 STATIC INLINE BOOL OsProtMprotectPermCheck(unsigned long prot, LosVmMapRegion *region)
177 {
178 UINT32 protFlags = 0;
179 UINT32 permFlags = 0;
180 UINT32 fileFlags = region->unTypeData.rf.f_oflags;
181 permFlags |= ((fileFlags & O_ACCMODE) ^ O_RDONLY) ? 0 : VM_MAP_REGION_FLAG_PERM_READ;
182 permFlags |= (fileFlags & O_WRONLY) ? VM_MAP_REGION_FLAG_PERM_WRITE : 0;
183 permFlags |= (fileFlags & O_RDWR) ? (VM_MAP_REGION_FLAG_PERM_READ | VM_MAP_REGION_FLAG_PERM_WRITE) : 0;
184 protFlags |= (prot & PROT_READ) ? VM_MAP_REGION_FLAG_PERM_READ : 0;
185 protFlags |= (prot & PROT_WRITE) ? VM_MAP_REGION_FLAG_PERM_WRITE : 0;
186
187 return ((protFlags & permFlags) == protFlags);
188 }
189
OsShrinkHeap(VOID * addr,LosVmSpace * space)190 VOID *OsShrinkHeap(VOID *addr, LosVmSpace *space)
191 {
192 VADDR_T newBrk, oldBrk;
193
194 newBrk = LOS_Align((VADDR_T)(UINTPTR)addr, PAGE_SIZE);
195 oldBrk = LOS_Align(space->heapNow, PAGE_SIZE);
196 if (LOS_UnMMap(newBrk, (oldBrk - newBrk)) < 0) {
197 return (void *)(UINTPTR)space->heapNow;
198 }
199 space->heapNow = (VADDR_T)(UINTPTR)addr;
200 return addr;
201 }
202
LOS_DoBrk(VOID * addr)203 VOID *LOS_DoBrk(VOID *addr)
204 {
205 LosVmSpace *space = OsCurrProcessGet()->vmSpace;
206 size_t size;
207 VOID *ret = NULL;
208 LosVmMapRegion *region = NULL;
209 VOID *alignAddr = NULL;
210 VOID *shrinkAddr = NULL;
211
212 if (addr == NULL) {
213 return (void *)(UINTPTR)space->heapNow;
214 }
215
216 if ((UINTPTR)addr < (UINTPTR)space->heapBase) {
217 return (VOID *)-ENOMEM;
218 }
219
220 size = (UINTPTR)addr - (UINTPTR)space->heapBase;
221 size = ROUNDUP(size, PAGE_SIZE);
222 alignAddr = (CHAR *)(UINTPTR)(space->heapBase) + size;
223 PRINT_INFO("brk addr %p , size 0x%x, alignAddr %p, align %d\n", addr, size, alignAddr, PAGE_SIZE);
224
225 (VOID)LOS_MuxAcquire(&space->regionMux);
226 if (addr < (VOID *)(UINTPTR)space->heapNow) {
227 shrinkAddr = OsShrinkHeap(addr, space);
228 (VOID)LOS_MuxRelease(&space->regionMux);
229 return shrinkAddr;
230 }
231
232 if ((UINTPTR)alignAddr >= space->mapBase) {
233 VM_ERR("Process heap memory space is insufficient");
234 ret = (VOID *)-ENOMEM;
235 goto REGION_ALLOC_FAILED;
236 }
237
238 if (space->heapBase == space->heapNow) {
239 region = LOS_RegionAlloc(space, space->heapBase, size,
240 VM_MAP_REGION_FLAG_PERM_READ | VM_MAP_REGION_FLAG_PERM_WRITE |
241 VM_MAP_REGION_FLAG_FIXED | VM_MAP_REGION_FLAG_PERM_USER, 0);
242 if (region == NULL) {
243 ret = (VOID *)-ENOMEM;
244 VM_ERR("LOS_RegionAlloc failed");
245 goto REGION_ALLOC_FAILED;
246 }
247 region->regionFlags |= VM_MAP_REGION_FLAG_HEAP;
248 space->heap = region;
249 }
250
251 space->heapNow = (VADDR_T)(UINTPTR)alignAddr;
252 space->heap->range.size = size;
253 ret = (VOID *)(UINTPTR)space->heapNow;
254
255 REGION_ALLOC_FAILED:
256 (VOID)LOS_MuxRelease(&space->regionMux);
257 return ret;
258 }
259
OsInheritOldRegionName(UINT32 oldRegionFlags)260 STATIC UINT32 OsInheritOldRegionName(UINT32 oldRegionFlags)
261 {
262 UINT32 vmFlags = 0;
263
264 if (oldRegionFlags & VM_MAP_REGION_FLAG_HEAP) {
265 vmFlags |= VM_MAP_REGION_FLAG_HEAP;
266 } else if (oldRegionFlags & VM_MAP_REGION_FLAG_STACK) {
267 vmFlags |= VM_MAP_REGION_FLAG_STACK;
268 } else if (oldRegionFlags & VM_MAP_REGION_FLAG_TEXT) {
269 vmFlags |= VM_MAP_REGION_FLAG_TEXT;
270 } else if (oldRegionFlags & VM_MAP_REGION_FLAG_VDSO) {
271 vmFlags |= VM_MAP_REGION_FLAG_VDSO;
272 } else if (oldRegionFlags & VM_MAP_REGION_FLAG_MMAP) {
273 vmFlags |= VM_MAP_REGION_FLAG_MMAP;
274 } else if (oldRegionFlags & VM_MAP_REGION_FLAG_SHM) {
275 vmFlags |= VM_MAP_REGION_FLAG_SHM;
276 }
277
278 return vmFlags;
279 }
280
LOS_DoMprotect(VADDR_T vaddr,size_t len,unsigned long prot)281 INT32 LOS_DoMprotect(VADDR_T vaddr, size_t len, unsigned long prot)
282 {
283 LosVmSpace *space = OsCurrProcessGet()->vmSpace;
284 LosVmMapRegion *region = NULL;
285 UINT32 vmFlags;
286 UINT32 count;
287 int ret;
288
289 (VOID)LOS_MuxAcquire(&space->regionMux);
290 region = LOS_RegionFind(space, vaddr);
291 if (!IS_ALIGNED(vaddr, PAGE_SIZE) || (region == NULL) || (vaddr > vaddr + len)) {
292 ret = -EINVAL;
293 goto OUT_MPROTECT;
294 }
295
296 if ((prot & ~(PROT_READ | PROT_WRITE | PROT_EXEC))) {
297 ret = -EINVAL;
298 goto OUT_MPROTECT;
299 }
300
301 if ((region->regionFlags & VM_MAP_REGION_FLAG_VDSO) || (region->regionFlags & VM_MAP_REGION_FLAG_HEAP)) {
302 ret = -EPERM;
303 goto OUT_MPROTECT;
304 }
305
306 if (LOS_IsRegionTypeFile(region) && (region->regionFlags & VM_MAP_REGION_FLAG_SHARED)) {
307 if (!OsProtMprotectPermCheck(prot, region)) {
308 ret = -EACCES;
309 goto OUT_MPROTECT;
310 }
311 }
312
313 len = LOS_Align(len, PAGE_SIZE);
314 /* can't operation cross region */
315 if ((region->range.base + region->range.size) < (vaddr + len)) {
316 ret = -EINVAL;
317 goto OUT_MPROTECT;
318 }
319
320 /* if only move some part of region, we need to split first */
321 if (region->range.size > len) {
322 OsVmRegionAdjust(space, vaddr, len);
323 }
324
325 vmFlags = OsCvtProtFlagsToRegionFlags(prot, 0);
326 vmFlags |= (region->regionFlags & VM_MAP_REGION_FLAG_SHARED) ? VM_MAP_REGION_FLAG_SHARED : 0;
327 vmFlags |= OsInheritOldRegionName(region->regionFlags);
328 region = LOS_RegionFind(space, vaddr);
329 if (region == NULL) {
330 ret = -ENOMEM;
331 goto OUT_MPROTECT;
332 }
333 region->regionFlags = vmFlags;
334 count = len >> PAGE_SHIFT;
335 ret = LOS_ArchMmuChangeProt(&space->archMmu, vaddr, count, region->regionFlags);
336 if (ret) {
337 ret = -ENOMEM;
338 goto OUT_MPROTECT;
339 }
340 ret = LOS_OK;
341
342 OUT_MPROTECT:
343 #ifdef LOSCFG_VM_OVERLAP_CHECK
344 if (VmmAspaceRegionsOverlapCheck(aspace) < 0) {
345 (VOID)OsShellCmdDumpVm(0, NULL);
346 ret = -ENOMEM;
347 }
348 #endif
349
350 (VOID)LOS_MuxRelease(&space->regionMux);
351 return ret;
352 }
353
OsMremapCheck(VADDR_T addr,size_t oldLen,VADDR_T newAddr,size_t newLen,unsigned int flags)354 STATUS_T OsMremapCheck(VADDR_T addr, size_t oldLen, VADDR_T newAddr, size_t newLen, unsigned int flags)
355 {
356 LosVmSpace *space = OsCurrProcessGet()->vmSpace;
357 LosVmMapRegion *region = LOS_RegionFind(space, addr);
358 VADDR_T regionEnd;
359
360 if ((region == NULL) || (region->range.base > addr) || (newLen == 0)) {
361 return -EINVAL;
362 }
363
364 if (flags & ~(MREMAP_FIXED | MREMAP_MAYMOVE)) {
365 return -EINVAL;
366 }
367
368 if (((flags & MREMAP_FIXED) == MREMAP_FIXED) && ((flags & MREMAP_MAYMOVE) == 0)) {
369 return -EINVAL;
370 }
371
372 if (!IS_ALIGNED(addr, PAGE_SIZE)) {
373 return -EINVAL;
374 }
375
376 regionEnd = region->range.base + region->range.size;
377
378 /* we can't operate across region */
379 if (oldLen > regionEnd - addr) {
380 return -EFAULT;
381 }
382
383 /* avoiding overflow */
384 if (newLen > oldLen) {
385 if ((addr + newLen) < addr) {
386 return -EINVAL;
387 }
388 }
389
390 /* avoid new region overlapping with the old one */
391 if (flags & MREMAP_FIXED) {
392 if (((region->range.base + region->range.size) > newAddr) &&
393 (region->range.base < (newAddr + newLen))) {
394 return -EINVAL;
395 }
396
397 if (!IS_ALIGNED(newAddr, PAGE_SIZE)) {
398 return -EINVAL;
399 }
400 }
401
402 return LOS_OK;
403 }
404
LOS_DoMremap(VADDR_T oldAddress,size_t oldSize,size_t newSize,int flags,VADDR_T newAddr)405 VADDR_T LOS_DoMremap(VADDR_T oldAddress, size_t oldSize, size_t newSize, int flags, VADDR_T newAddr)
406 {
407 LosVmMapRegion *regionOld = NULL;
408 LosVmMapRegion *regionNew = NULL;
409 STATUS_T status;
410 VADDR_T ret;
411 LosVmSpace *space = OsCurrProcessGet()->vmSpace;
412
413 oldSize = LOS_Align(oldSize, PAGE_SIZE);
414 newSize = LOS_Align(newSize, PAGE_SIZE);
415
416 (VOID)LOS_MuxAcquire(&space->regionMux);
417
418 status = OsMremapCheck(oldAddress, oldSize, newAddr, newSize, (unsigned int)flags);
419 if (status) {
420 ret = status;
421 goto OUT_MREMAP;
422 }
423
424 /* if only move some part of region, we need to split first */
425 status = OsVmRegionAdjust(space, oldAddress, oldSize);
426 if (status) {
427 ret = -ENOMEM;
428 goto OUT_MREMAP;
429 }
430
431 regionOld = LOS_RegionFind(space, oldAddress);
432 if (regionOld == NULL) {
433 ret = -ENOMEM;
434 goto OUT_MREMAP;
435 }
436
437 if ((unsigned int)flags & MREMAP_FIXED) {
438 regionNew = OsVmRegionDup(space, regionOld, newAddr, newSize);
439 if (!regionNew) {
440 ret = -ENOMEM;
441 goto OUT_MREMAP;
442 }
443 status = LOS_ArchMmuMove(&space->archMmu, oldAddress, newAddr,
444 ((newSize < regionOld->range.size) ? newSize : regionOld->range.size) >> PAGE_SHIFT,
445 regionOld->regionFlags);
446 if (status) {
447 LOS_RegionFree(space, regionNew);
448 ret = -ENOMEM;
449 goto OUT_MREMAP;
450 }
451 LOS_RegionFree(space, regionOld);
452 ret = newAddr;
453 goto OUT_MREMAP;
454 }
455 // take it as shrink operation
456 if (oldSize > newSize) {
457 LOS_UnMMap(oldAddress + newSize, oldSize - newSize);
458 ret = oldAddress;
459 goto OUT_MREMAP;
460 }
461 status = OsIsRegionCanExpand(space, regionOld, newSize);
462 // we can expand directly.
463 if (!status) {
464 regionOld->range.size = newSize;
465 ret = oldAddress;
466 goto OUT_MREMAP;
467 }
468
469 if ((unsigned int)flags & MREMAP_MAYMOVE) {
470 regionNew = OsVmRegionDup(space, regionOld, 0, newSize);
471 if (regionNew == NULL) {
472 ret = -ENOMEM;
473 goto OUT_MREMAP;
474 }
475 status = LOS_ArchMmuMove(&space->archMmu, oldAddress, regionNew->range.base,
476 regionOld->range.size >> PAGE_SHIFT, regionOld->regionFlags);
477 if (status) {
478 LOS_RegionFree(space, regionNew);
479 ret = -ENOMEM;
480 goto OUT_MREMAP;
481 }
482 LOS_RegionFree(space, regionOld);
483 ret = regionNew->range.base;
484 goto OUT_MREMAP;
485 }
486
487 ret = -EINVAL;
488 OUT_MREMAP:
489 #ifdef LOSCFG_VM_OVERLAP_CHECK
490 if (VmmAspaceRegionsOverlapCheck(aspace) < 0) {
491 (VOID)OsShellCmdDumpVm(0, NULL);
492 ret = -ENOMEM;
493 }
494 #endif
495
496 (VOID)LOS_MuxRelease(&space->regionMux);
497 return ret;
498 }
499
LOS_DumpMemRegion(VADDR_T vaddr)500 VOID LOS_DumpMemRegion(VADDR_T vaddr)
501 {
502 LosVmSpace *space = NULL;
503
504 space = OsCurrProcessGet()->vmSpace;
505 if (space == NULL) {
506 return;
507 }
508
509 if (LOS_IsRangeInSpace(space, ROUNDDOWN(vaddr, MB), MB) == FALSE) {
510 return;
511 }
512
513 OsDumpPte(vaddr);
514 OsDumpAspace(space);
515 }
516 #endif
517
518