/* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "berberis/kernel_api/sys_mman_emulation.h" #include #include #include "berberis/base/mmap.h" #include "berberis/base/prctl_helpers.h" #include "berberis/base/tracing.h" #include "berberis/guest_os_primitives/guest_map_shadow.h" #include "berberis/guest_state/guest_addr.h" namespace berberis { namespace { int ToHostProt(int guest_prot) { if (guest_prot & PROT_EXEC) { // Guest EXEC should _not_ be host EXEC but should be host READ! return (guest_prot & ~PROT_EXEC) | PROT_READ; } return guest_prot; } // Clobbers errno. void UpdateGuestProt(int guest_prot, void* addr, size_t length) { GuestAddr guest_addr = ToGuestAddr(addr); GuestMapShadow* shadow = GuestMapShadow::GetInstance(); if (guest_prot & PROT_EXEC) { // Since we strip guest executable bit from host mappings kernel may merge r-- and r-x guest // mappings together, which is difficult to split back when emulating /proc/self/maps. Setting // region name helps to prevent regions merging. It helps even if it's a file backed mapping, // even though filename isn't visibly changed in /proc/self/maps in this case. // Note that this name can be overridden by the app, which is fine as long as it's // unique for this mapping. We do not remove this name if executable bit is // removed which also should be fine since it's just a hint. int res = SetVmaAnonName(addr, AlignUpPageSize(length), "[guest exec mapping hint]"); if (res == -1) { TRACE("PR_SET_VMA_ANON_NAME failed with errno=%s", std::strerror(errno)); } shadow->SetExecutable(guest_addr, length); } else { shadow->ClearExecutable(guest_addr, length); } } } // namespace // ATTENTION: the order of mmap/mprotect/munmap and SetExecutable/ClearExecutable is essential! // // The issue here is that threads might be executing the code being munmap'ed or mprotect'ed. // SetExecutable/ClearExecutable should flush code cache and notify threads to restart. // If other thread starts translation after actual mmap/mprotect/munmap but before xbit update, // it might pick up an already obsolete code. void* MmapForGuest(void* addr, size_t length, int prot, int flags, int fd, off64_t offset) { void* result = mmap64(addr, length, ToHostProt(prot), flags, fd, offset); if (result != MAP_FAILED) { UpdateGuestProt(prot, result, length); } return result; } int MunmapForGuest(void* addr, size_t length) { GuestMapShadow::GetInstance()->ClearExecutable(ToGuestAddr(addr), length); return munmap(addr, length); } int MprotectForGuest(void* addr, size_t length, int prot) { // In b/218772975 the app is scanning "/proc/self/maps" and tries to mprotect // mappings for some libraries found there (for unknown reason) effectively removing // execution permission. GuestMapShadow is pre-populated with such mappings, so we // suppress guest mprotect for them. if (GuestMapShadow::GetInstance()->IntersectsWithProtectedMapping( addr, static_cast(addr) + length)) { TRACE("Suppressing guest mprotect(%p, %zu) on a mapping protected from guest", addr, length); errno = EACCES; return -1; } UpdateGuestProt(prot, addr, length); return mprotect(addr, length, ToHostProt(prot)); } void* MremapForGuest(void* old_addr, size_t old_size, size_t new_size, int flags, void* new_addr) { // As we drop xbit for host mmap calls, host mappings might differ from guest // mappings, and host mremap might work when guest mremap should not. Check in // advance to avoid that. Rules for checks: // 1. Shrink without MREMAP_FIXED - always Ok. // 2. Shrink with MREMAP_FIXED - needs consistent permissions within new_size. // 3. Grow - needs consistent permissions within old_size. GuestMapShadow* shadow = GuestMapShadow::GetInstance(); if (new_size <= old_size) { if ((flags & MREMAP_FIXED) && shadow->GetExecutable(ToGuestAddr(old_addr), new_size) == kBitMixed) { errno = EFAULT; return MAP_FAILED; } } else { if (shadow->GetExecutable(ToGuestAddr(old_addr), old_size) == kBitMixed) { errno = EFAULT; return MAP_FAILED; } } void* result = mremap(old_addr, old_size, new_size, flags, new_addr); if (result != MAP_FAILED) { shadow->RemapExecutable(ToGuestAddr(old_addr), old_size, ToGuestAddr(result), new_size); } return result; } } // namespace berberis