Untag user addresses before handling them in the Sentry

Top-Byte-Ignore (TBI) is a feature on all ARMv8.0 CPUs that causes the top byte
of virtual addresses to be ignored on loads and stores. Instead, bit 55 is
extended over bits 56-63 before address translation. This feature allows use of
the (ignored) top byte as a tag or for other in-band metadata.

In Linux, brk()/mmap()/mremap() syscalls don't untag addresses. More details
are in dcde237319e6 ("mm: Avoid creating virtual address aliases in
brk()/mmap()/mremap()")

PiperOrigin-RevId: 715885990
This commit is contained in:
Andrei Vagin
2025-01-15 11:52:40 -08:00
committed by gVisor bot
parent 3649ca9d9e
commit 1864d9d091
8 changed files with 40 additions and 1 deletions
+6
View File
@@ -86,6 +86,12 @@ func ESRAccessType(code uint64) AccessType {
}
}
// UntaggedUserAddr clears the tag from the address pointer. Top-Byte-Ignore (TBI0)
// is enabled in Linux, so bits[63:56] of user space addresses are ignored.
func UntaggedUserAddr(addr Addr) Addr {
return Addr(int64(addr<<8) >> 8)
}
func init() {
// Make sure the page size is 4K on arm64 platform.
if size := unix.Getpagesize(); size != PageSize {
+5
View File
@@ -43,3 +43,8 @@ var (
// ByteOrder is the native byte order (little endian).
ByteOrder = binary.LittleEndian
)
// UntaggedUserAddr is no-op on x86.
func UntaggedUserAddr(addr Addr) Addr {
return addr
}
+1
View File
@@ -328,6 +328,7 @@ const (
// getKey returns a Key representing address addr in c.
func getKey(t Target, addr hostarch.Addr, private bool) (Key, error) {
addr = hostarch.UntaggedUserAddr(addr)
// Ensure the address is aligned.
// It must be a DWORD boundary.
if addr&0x3 != 0 {
+1
View File
@@ -70,6 +70,7 @@ const (
//
// Preconditions: length >= 0.
func (mm *MemoryManager) CheckIORange(addr hostarch.Addr, length int64) (hostarch.AddrRange, bool) {
addr = hostarch.UntaggedUserAddr(addr)
// Note that access_ok() constrains end even if length == 0.
ar, ok := addr.ToRange(uint64(length))
return ar, (ok && ar.End <= mm.layout.MaxAddr)
+9
View File
@@ -33,6 +33,7 @@ import (
//
// Preconditions: mm.as != nil.
func (mm *MemoryManager) HandleUserFault(ctx context.Context, addr hostarch.Addr, at hostarch.AccessType, sp hostarch.Addr) error {
addr = hostarch.UntaggedUserAddr(addr)
ar, ok := addr.RoundDown().ToRange(hostarch.PageSize)
if !ok {
return linuxerr.EFAULT
@@ -304,6 +305,7 @@ func (mm *MemoryManager) MapStack(ctx context.Context) (hostarch.AddrRange, erro
// MUnmap implements the semantics of Linux's munmap(2).
func (mm *MemoryManager) MUnmap(ctx context.Context, addr hostarch.Addr, length uint64) error {
addr = hostarch.UntaggedUserAddr(addr)
if addr != addr.RoundDown() {
return linuxerr.EINVAL
}
@@ -358,6 +360,8 @@ const (
// MRemap implements the semantics of Linux's mremap(2).
func (mm *MemoryManager) MRemap(ctx context.Context, oldAddr hostarch.Addr, oldSize uint64, newSize uint64, opts MRemapOpts) (hostarch.Addr, error) {
oldAddr = hostarch.UntaggedUserAddr(oldAddr)
// "Note that old_address has to be page aligned." - mremap(2)
if oldAddr.RoundDown() != oldAddr {
return 0, linuxerr.EINVAL
@@ -627,6 +631,7 @@ func (mm *MemoryManager) MRemap(ctx context.Context, oldAddr hostarch.Addr, oldS
// MProtect implements the semantics of Linux's mprotect(2).
func (mm *MemoryManager) MProtect(addr hostarch.Addr, length uint64, realPerms hostarch.AccessType, growsDown bool) error {
addr = hostarch.UntaggedUserAddr(addr)
if addr.RoundDown() != addr {
return linuxerr.EINVAL
}
@@ -831,6 +836,7 @@ func (mm *MemoryManager) Brk(ctx context.Context, addr hostarch.Addr) (hostarch.
// MLock implements the semantics of Linux's mlock()/mlock2()/munlock(),
// depending on mode.
func (mm *MemoryManager) MLock(ctx context.Context, addr hostarch.Addr, length uint64, mode memmap.MLockMode) error {
addr = hostarch.UntaggedUserAddr(addr)
// Linux allows this to overflow.
la, _ := hostarch.Addr(length + addr.PageOffset()).RoundUp()
ar, ok := addr.RoundDown().ToRange(uint64(la))
@@ -1101,6 +1107,7 @@ func madviseAddrRange(addr hostarch.Addr, length uint64) (hostarch.AddrRange, er
// Decommit implements the semantics of Linux's madvise(MADV_DONTNEED).
func (mm *MemoryManager) Decommit(addr hostarch.Addr, length uint64) error {
addr = hostarch.UntaggedUserAddr(addr)
ar, err := madviseAddrRange(addr, length)
if err != nil {
return err
@@ -1293,6 +1300,7 @@ func (mm *MemoryManager) madviseMutateVMAs(addr hostarch.Addr, length uint64, f
//
// Preconditions: addr and length are page-aligned.
func (mm *MemoryManager) SetDontFork(addr hostarch.Addr, length uint64, dontfork bool) error {
addr = hostarch.UntaggedUserAddr(addr)
return mm.madviseMutateVMAs(addr, length, func(vseg vmaIterator) error {
vseg.ValuePtr().dontfork = dontfork
return nil
@@ -1344,6 +1352,7 @@ type MSyncOpts struct {
// MSync implements the semantics of Linux's msync().
func (mm *MemoryManager) MSync(ctx context.Context, addr hostarch.Addr, length uint64, opts MSyncOpts) error {
addr = hostarch.UntaggedUserAddr(addr)
if addr != addr.RoundDown() {
return linuxerr.EINVAL
}
@@ -83,7 +83,8 @@ func (c *vCPU) initArchState() error {
}
// tcr_el1
data = _TCR_TXSZ_VA48 | _TCR_CACHE_FLAGS | _TCR_SHARED | _TCR_TG_FLAGS | _TCR_ASID16 | _TCR_IPS_40BITS
data = _TCR_TXSZ_VA48 | _TCR_CACHE_FLAGS | _TCR_SHARED | _TCR_TG_FLAGS |
_TCR_ASID16 | _TCR_IPS_40BITS | _TCR_TBI0
reg.id = _KVM_ARM64_REGS_TCR_EL1
if err := c.setOneRegister(&reg); err != nil {
return err
+1
View File
@@ -214,6 +214,7 @@ func Mincore(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr
length := args[1].SizeT()
vec := args[2].Pointer()
addr = hostarch.UntaggedUserAddr(addr)
if addr != addr.RoundDown() {
return 0, nil, linuxerr.EINVAL
}
+15
View File
@@ -14,6 +14,7 @@
#define _GNU_SOURCE 1
#include <signal.h>
#include <sys/mman.h>
#include <ucontext.h>
#include <unistd.h>
@@ -63,6 +64,20 @@ void sigact_handler(int sig, siginfo_t* siginfo, void* context) {
}
}
#if defined(__aarch64__)
#define APPLY_ADDRESS_TAG(addr) ((void*)((uint64_t)addr | (1ULL << 57)))
TEST(TaggedAddressesTest, MemoryFault) {
void* addr = mmap(0, kPageSize, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
ASSERT_NE(addr, MAP_FAILED);
addr = APPLY_ADDRESS_TAG(addr);
((uint64_t*)addr)[0] =
5; // trigger a memory fault that is handled in the Sentry.
EXPECT_THAT(munmap(addr, kPageSize), SyscallSucceeds());
}
#endif
TEST(FaultTest, InRange) {
// Reset the signal handler to do nothing so that it doesn't freak out
// the test runner when we fire an alarm.