diff options
Diffstat (limited to 'pkg/sentry/memmap')
-rw-r--r-- | pkg/sentry/memmap/BUILD | 55 | ||||
-rw-r--r-- | pkg/sentry/memmap/mapping_set.go | 253 | ||||
-rw-r--r-- | pkg/sentry/memmap/mapping_set_test.go | 260 | ||||
-rw-r--r-- | pkg/sentry/memmap/memmap.go | 363 |
4 files changed, 931 insertions, 0 deletions
diff --git a/pkg/sentry/memmap/BUILD b/pkg/sentry/memmap/BUILD new file mode 100644 index 000000000..a98b66de1 --- /dev/null +++ b/pkg/sentry/memmap/BUILD @@ -0,0 +1,55 @@ +load("//tools:defs.bzl", "go_library", "go_test") +load("//tools/go_generics:defs.bzl", "go_template_instance") + +package(licenses = ["notice"]) + +go_template_instance( + name = "mappable_range", + out = "mappable_range.go", + package = "memmap", + prefix = "Mappable", + template = "//pkg/segment:generic_range", + types = { + "T": "uint64", + }, +) + +go_template_instance( + name = "mapping_set_impl", + out = "mapping_set_impl.go", + package = "memmap", + prefix = "Mapping", + template = "//pkg/segment:generic_set", + types = { + "Key": "uint64", + "Range": "MappableRange", + "Value": "MappingsOfRange", + "Functions": "mappingSetFunctions", + }, +) + +go_library( + name = "memmap", + srcs = [ + "mappable_range.go", + "mapping_set.go", + "mapping_set_impl.go", + "memmap.go", + ], + visibility = ["//pkg/sentry:internal"], + deps = [ + "//pkg/context", + "//pkg/log", + "//pkg/sentry/platform", + "//pkg/syserror", + "//pkg/usermem", + ], +) + +go_test( + name = "memmap_test", + size = "small", + srcs = ["mapping_set_test.go"], + library = ":memmap", + deps = ["//pkg/usermem"], +) diff --git a/pkg/sentry/memmap/mapping_set.go b/pkg/sentry/memmap/mapping_set.go new file mode 100644 index 000000000..d609c1ae0 --- /dev/null +++ b/pkg/sentry/memmap/mapping_set.go @@ -0,0 +1,253 @@ +// Copyright 2018 The gVisor Authors. +// +// 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. + +package memmap + +import ( + "fmt" + "math" + + "gvisor.dev/gvisor/pkg/usermem" +) + +// MappingSet maps offsets into a Mappable to mappings of those offsets. It is +// used to implement Mappable.AddMapping and RemoveMapping for Mappables that +// may need to call MappingSpace.Invalidate. +// +// type MappingSet <generated by go_generics> + +// MappingsOfRange is the value type of MappingSet, and represents the set of +// all mappings of the corresponding MappableRange. +// +// Using a map offers O(1) lookups in RemoveMapping and +// mappingSetFunctions.Merge. +type MappingsOfRange map[MappingOfRange]struct{} + +// MappingOfRange represents a mapping of a MappableRange. +// +// +stateify savable +type MappingOfRange struct { + MappingSpace MappingSpace + AddrRange usermem.AddrRange + Writable bool +} + +func (r MappingOfRange) invalidate(opts InvalidateOpts) { + r.MappingSpace.Invalidate(r.AddrRange, opts) +} + +// String implements fmt.Stringer.String. +func (r MappingOfRange) String() string { + return fmt.Sprintf("%#v", r.AddrRange) +} + +// mappingSetFunctions implements segment.Functions for MappingSet. +type mappingSetFunctions struct{} + +// MinKey implements segment.Functions.MinKey. +func (mappingSetFunctions) MinKey() uint64 { + return 0 +} + +// MaxKey implements segment.Functions.MaxKey. +func (mappingSetFunctions) MaxKey() uint64 { + return math.MaxUint64 +} + +// ClearValue implements segment.Functions.ClearValue. +func (mappingSetFunctions) ClearValue(v *MappingsOfRange) { + *v = MappingsOfRange{} +} + +// Merge implements segment.Functions.Merge. +// +// Since each value is a map of MappingOfRanges, values can only be merged if +// all MappingOfRanges in each map have an exact pair in the other map, forming +// one contiguous region. +func (mappingSetFunctions) Merge(r1 MappableRange, val1 MappingsOfRange, r2 MappableRange, val2 MappingsOfRange) (MappingsOfRange, bool) { + if len(val1) != len(val2) { + return nil, false + } + + merged := make(MappingsOfRange, len(val1)) + + // Each MappingOfRange in val1 must have a matching region in val2, forming + // one contiguous region. + for k1 := range val1 { + // We expect val2 to contain a key that forms a contiguous + // region with k1. + k2 := MappingOfRange{ + MappingSpace: k1.MappingSpace, + AddrRange: usermem.AddrRange{ + Start: k1.AddrRange.End, + End: k1.AddrRange.End + usermem.Addr(r2.Length()), + }, + Writable: k1.Writable, + } + if _, ok := val2[k2]; !ok { + return nil, false + } + + // OK. Add it to the merged map. + merged[MappingOfRange{ + MappingSpace: k1.MappingSpace, + AddrRange: usermem.AddrRange{ + Start: k1.AddrRange.Start, + End: k2.AddrRange.End, + }, + Writable: k1.Writable, + }] = struct{}{} + } + + return merged, true +} + +// Split implements segment.Functions.Split. +func (mappingSetFunctions) Split(r MappableRange, val MappingsOfRange, split uint64) (MappingsOfRange, MappingsOfRange) { + if split <= r.Start || split >= r.End { + panic(fmt.Sprintf("split is not within range %v", r)) + } + + m1 := make(MappingsOfRange, len(val)) + m2 := make(MappingsOfRange, len(val)) + + // split is a value in MappableRange, we need the offset into the + // corresponding MappingsOfRange. + offset := usermem.Addr(split - r.Start) + for k := range val { + k1 := MappingOfRange{ + MappingSpace: k.MappingSpace, + AddrRange: usermem.AddrRange{ + Start: k.AddrRange.Start, + End: k.AddrRange.Start + offset, + }, + Writable: k.Writable, + } + m1[k1] = struct{}{} + + k2 := MappingOfRange{ + MappingSpace: k.MappingSpace, + AddrRange: usermem.AddrRange{ + Start: k.AddrRange.Start + offset, + End: k.AddrRange.End, + }, + Writable: k.Writable, + } + m2[k2] = struct{}{} + } + + return m1, m2 +} + +// subsetMapping returns the MappingOfRange that maps subsetRange, given that +// ms maps wholeRange beginning at addr. +// +// For instance, suppose wholeRange = [0x0, 0x2000) and addr = 0x4000, +// indicating that ms maps addresses [0x4000, 0x6000) to MappableRange [0x0, +// 0x2000). Then for subsetRange = [0x1000, 0x2000), subsetMapping returns a +// MappingOfRange for which AddrRange = [0x5000, 0x6000). +func subsetMapping(wholeRange, subsetRange MappableRange, ms MappingSpace, addr usermem.Addr, writable bool) MappingOfRange { + if !wholeRange.IsSupersetOf(subsetRange) { + panic(fmt.Sprintf("%v is not a superset of %v", wholeRange, subsetRange)) + } + + offset := subsetRange.Start - wholeRange.Start + start := addr + usermem.Addr(offset) + return MappingOfRange{ + MappingSpace: ms, + AddrRange: usermem.AddrRange{ + Start: start, + End: start + usermem.Addr(subsetRange.Length()), + }, + Writable: writable, + } +} + +// AddMapping adds the given mapping and returns the set of MappableRanges that +// previously had no mappings. +// +// Preconditions: As for Mappable.AddMapping. +func (s *MappingSet) AddMapping(ms MappingSpace, ar usermem.AddrRange, offset uint64, writable bool) []MappableRange { + mr := MappableRange{offset, offset + uint64(ar.Length())} + var mapped []MappableRange + seg, gap := s.Find(mr.Start) + for { + switch { + case seg.Ok() && seg.Start() < mr.End: + seg = s.Isolate(seg, mr) + seg.Value()[subsetMapping(mr, seg.Range(), ms, ar.Start, writable)] = struct{}{} + seg, gap = seg.NextNonEmpty() + + case gap.Ok() && gap.Start() < mr.End: + gapMR := gap.Range().Intersect(mr) + mapped = append(mapped, gapMR) + // Insert a set and continue from the above case. + seg, gap = s.Insert(gap, gapMR, make(MappingsOfRange)), MappingGapIterator{} + + default: + return mapped + } + } +} + +// RemoveMapping removes the given mapping and returns the set of +// MappableRanges that now have no mappings. +// +// Preconditions: As for Mappable.RemoveMapping. +func (s *MappingSet) RemoveMapping(ms MappingSpace, ar usermem.AddrRange, offset uint64, writable bool) []MappableRange { + mr := MappableRange{offset, offset + uint64(ar.Length())} + var unmapped []MappableRange + + seg := s.FindSegment(mr.Start) + if !seg.Ok() { + panic(fmt.Sprintf("MappingSet.RemoveMapping(%v): no segment containing %#x: %v", mr, mr.Start, s)) + } + for seg.Ok() && seg.Start() < mr.End { + // Ensure this segment is limited to our range. + seg = s.Isolate(seg, mr) + + // Remove this part of the mapping. + mappings := seg.Value() + delete(mappings, subsetMapping(mr, seg.Range(), ms, ar.Start, writable)) + + if len(mappings) == 0 { + unmapped = append(unmapped, seg.Range()) + seg = s.Remove(seg).NextSegment() + } else { + seg = seg.NextSegment() + } + } + s.MergeAdjacent(mr) + return unmapped +} + +// Invalidate calls MappingSpace.Invalidate for all mappings of offsets in mr. +func (s *MappingSet) Invalidate(mr MappableRange, opts InvalidateOpts) { + for seg := s.LowerBoundSegment(mr.Start); seg.Ok() && seg.Start() < mr.End; seg = seg.NextSegment() { + segMR := seg.Range() + for m := range seg.Value() { + region := subsetMapping(segMR, segMR.Intersect(mr), m.MappingSpace, m.AddrRange.Start, m.Writable) + region.invalidate(opts) + } + } +} + +// InvalidateAll calls MappingSpace.Invalidate for all mappings of s. +func (s *MappingSet) InvalidateAll(opts InvalidateOpts) { + for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() { + for m := range seg.Value() { + m.invalidate(opts) + } + } +} diff --git a/pkg/sentry/memmap/mapping_set_test.go b/pkg/sentry/memmap/mapping_set_test.go new file mode 100644 index 000000000..d39efe38f --- /dev/null +++ b/pkg/sentry/memmap/mapping_set_test.go @@ -0,0 +1,260 @@ +// Copyright 2018 The gVisor Authors. +// +// 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. + +package memmap + +import ( + "reflect" + "testing" + + "gvisor.dev/gvisor/pkg/usermem" +) + +type testMappingSpace struct { + // Ideally we'd store the full ranges that were invalidated, rather + // than individual calls to Invalidate, as they are an implementation + // detail, but this is the simplest way for now. + inv []usermem.AddrRange +} + +func (n *testMappingSpace) reset() { + n.inv = []usermem.AddrRange{} +} + +func (n *testMappingSpace) Invalidate(ar usermem.AddrRange, opts InvalidateOpts) { + n.inv = append(n.inv, ar) +} + +func TestAddRemoveMapping(t *testing.T) { + set := MappingSet{} + ms := &testMappingSpace{} + + mapped := set.AddMapping(ms, usermem.AddrRange{0x10000, 0x12000}, 0x1000, true) + if got, want := mapped, []MappableRange{{0x1000, 0x3000}}; !reflect.DeepEqual(got, want) { + t.Errorf("AddMapping: got %+v, wanted %+v", got, want) + } + + // Mappings (usermem.AddrRanges => memmap.MappableRange): + // [0x10000, 0x12000) => [0x1000, 0x3000) + t.Log(&set) + + mapped = set.AddMapping(ms, usermem.AddrRange{0x20000, 0x21000}, 0x2000, true) + if len(mapped) != 0 { + t.Errorf("AddMapping: got %+v, wanted []", mapped) + } + + // Mappings: + // [0x10000, 0x11000) => [0x1000, 0x2000) + // [0x11000, 0x12000) and [0x20000, 0x21000) => [0x2000, 0x3000) + t.Log(&set) + + mapped = set.AddMapping(ms, usermem.AddrRange{0x30000, 0x31000}, 0x4000, true) + if got, want := mapped, []MappableRange{{0x4000, 0x5000}}; !reflect.DeepEqual(got, want) { + t.Errorf("AddMapping: got %+v, wanted %+v", got, want) + } + + // Mappings: + // [0x10000, 0x11000) => [0x1000, 0x2000) + // [0x11000, 0x12000) and [0x20000, 0x21000) => [0x2000, 0x3000) + // [0x30000, 0x31000) => [0x4000, 0x5000) + t.Log(&set) + + mapped = set.AddMapping(ms, usermem.AddrRange{0x12000, 0x15000}, 0x3000, true) + if got, want := mapped, []MappableRange{{0x3000, 0x4000}, {0x5000, 0x6000}}; !reflect.DeepEqual(got, want) { + t.Errorf("AddMapping: got %+v, wanted %+v", got, want) + } + + // Mappings: + // [0x10000, 0x11000) => [0x1000, 0x2000) + // [0x11000, 0x12000) and [0x20000, 0x21000) => [0x2000, 0x3000) + // [0x12000, 0x13000) => [0x3000, 0x4000) + // [0x13000, 0x14000) and [0x30000, 0x31000) => [0x4000, 0x5000) + // [0x14000, 0x15000) => [0x5000, 0x6000) + t.Log(&set) + + unmapped := set.RemoveMapping(ms, usermem.AddrRange{0x10000, 0x11000}, 0x1000, true) + if got, want := unmapped, []MappableRange{{0x1000, 0x2000}}; !reflect.DeepEqual(got, want) { + t.Errorf("RemoveMapping: got %+v, wanted %+v", got, want) + } + + // Mappings: + // [0x11000, 0x12000) and [0x20000, 0x21000) => [0x2000, 0x3000) + // [0x12000, 0x13000) => [0x3000, 0x4000) + // [0x13000, 0x14000) and [0x30000, 0x31000) => [0x4000, 0x5000) + // [0x14000, 0x15000) => [0x5000, 0x6000) + t.Log(&set) + + unmapped = set.RemoveMapping(ms, usermem.AddrRange{0x20000, 0x21000}, 0x2000, true) + if len(unmapped) != 0 { + t.Errorf("RemoveMapping: got %+v, wanted []", unmapped) + } + + // Mappings: + // [0x11000, 0x13000) => [0x2000, 0x4000) + // [0x13000, 0x14000) and [0x30000, 0x31000) => [0x4000, 0x5000) + // [0x14000, 0x15000) => [0x5000, 0x6000) + t.Log(&set) + + unmapped = set.RemoveMapping(ms, usermem.AddrRange{0x11000, 0x15000}, 0x2000, true) + if got, want := unmapped, []MappableRange{{0x2000, 0x4000}, {0x5000, 0x6000}}; !reflect.DeepEqual(got, want) { + t.Errorf("RemoveMapping: got %+v, wanted %+v", got, want) + } + + // Mappings: + // [0x30000, 0x31000) => [0x4000, 0x5000) + t.Log(&set) + + unmapped = set.RemoveMapping(ms, usermem.AddrRange{0x30000, 0x31000}, 0x4000, true) + if got, want := unmapped, []MappableRange{{0x4000, 0x5000}}; !reflect.DeepEqual(got, want) { + t.Errorf("RemoveMapping: got %+v, wanted %+v", got, want) + } +} + +func TestInvalidateWholeMapping(t *testing.T) { + set := MappingSet{} + ms := &testMappingSpace{} + + set.AddMapping(ms, usermem.AddrRange{0x10000, 0x11000}, 0, true) + // Mappings: + // [0x10000, 0x11000) => [0, 0x1000) + t.Log(&set) + set.Invalidate(MappableRange{0, 0x1000}, InvalidateOpts{}) + if got, want := ms.inv, []usermem.AddrRange{{0x10000, 0x11000}}; !reflect.DeepEqual(got, want) { + t.Errorf("Invalidate: got %+v, wanted %+v", got, want) + } +} + +func TestInvalidatePartialMapping(t *testing.T) { + set := MappingSet{} + ms := &testMappingSpace{} + + set.AddMapping(ms, usermem.AddrRange{0x10000, 0x13000}, 0, true) + // Mappings: + // [0x10000, 0x13000) => [0, 0x3000) + t.Log(&set) + set.Invalidate(MappableRange{0x1000, 0x2000}, InvalidateOpts{}) + if got, want := ms.inv, []usermem.AddrRange{{0x11000, 0x12000}}; !reflect.DeepEqual(got, want) { + t.Errorf("Invalidate: got %+v, wanted %+v", got, want) + } +} + +func TestInvalidateMultipleMappings(t *testing.T) { + set := MappingSet{} + ms := &testMappingSpace{} + + set.AddMapping(ms, usermem.AddrRange{0x10000, 0x11000}, 0, true) + set.AddMapping(ms, usermem.AddrRange{0x20000, 0x21000}, 0x2000, true) + // Mappings: + // [0x10000, 0x11000) => [0, 0x1000) + // [0x12000, 0x13000) => [0x2000, 0x3000) + t.Log(&set) + set.Invalidate(MappableRange{0, 0x3000}, InvalidateOpts{}) + if got, want := ms.inv, []usermem.AddrRange{{0x10000, 0x11000}, {0x20000, 0x21000}}; !reflect.DeepEqual(got, want) { + t.Errorf("Invalidate: got %+v, wanted %+v", got, want) + } +} + +func TestInvalidateOverlappingMappings(t *testing.T) { + set := MappingSet{} + ms1 := &testMappingSpace{} + ms2 := &testMappingSpace{} + + set.AddMapping(ms1, usermem.AddrRange{0x10000, 0x12000}, 0, true) + set.AddMapping(ms2, usermem.AddrRange{0x20000, 0x22000}, 0x1000, true) + // Mappings: + // ms1:[0x10000, 0x12000) => [0, 0x2000) + // ms2:[0x11000, 0x13000) => [0x1000, 0x3000) + t.Log(&set) + set.Invalidate(MappableRange{0x1000, 0x2000}, InvalidateOpts{}) + if got, want := ms1.inv, []usermem.AddrRange{{0x11000, 0x12000}}; !reflect.DeepEqual(got, want) { + t.Errorf("Invalidate: ms1: got %+v, wanted %+v", got, want) + } + if got, want := ms2.inv, []usermem.AddrRange{{0x20000, 0x21000}}; !reflect.DeepEqual(got, want) { + t.Errorf("Invalidate: ms1: got %+v, wanted %+v", got, want) + } +} + +func TestMixedWritableMappings(t *testing.T) { + set := MappingSet{} + ms := &testMappingSpace{} + + mapped := set.AddMapping(ms, usermem.AddrRange{0x10000, 0x12000}, 0x1000, true) + if got, want := mapped, []MappableRange{{0x1000, 0x3000}}; !reflect.DeepEqual(got, want) { + t.Errorf("AddMapping: got %+v, wanted %+v", got, want) + } + + // Mappings: + // [0x10000, 0x12000) writable => [0x1000, 0x3000) + t.Log(&set) + + mapped = set.AddMapping(ms, usermem.AddrRange{0x20000, 0x22000}, 0x2000, false) + if got, want := mapped, []MappableRange{{0x3000, 0x4000}}; !reflect.DeepEqual(got, want) { + t.Errorf("AddMapping: got %+v, wanted %+v", got, want) + } + + // Mappings: + // [0x10000, 0x11000) writable => [0x1000, 0x2000) + // [0x11000, 0x12000) writable and [0x20000, 0x21000) readonly => [0x2000, 0x3000) + // [0x21000, 0x22000) readonly => [0x3000, 0x4000) + t.Log(&set) + + // Unmap should fail because we specified the readonly map address range, but + // asked to unmap a writable segment. + unmapped := set.RemoveMapping(ms, usermem.AddrRange{0x20000, 0x21000}, 0x2000, true) + if len(unmapped) != 0 { + t.Errorf("RemoveMapping: got %+v, wanted []", unmapped) + } + + // Readonly mapping removed, but writable mapping still exists in the range, + // so no mappable range fully unmapped. + unmapped = set.RemoveMapping(ms, usermem.AddrRange{0x20000, 0x21000}, 0x2000, false) + if len(unmapped) != 0 { + t.Errorf("RemoveMapping: got %+v, wanted []", unmapped) + } + + // Mappings: + // [0x10000, 0x12000) writable => [0x1000, 0x3000) + // [0x21000, 0x22000) readonly => [0x3000, 0x4000) + t.Log(&set) + + unmapped = set.RemoveMapping(ms, usermem.AddrRange{0x11000, 0x12000}, 0x2000, true) + if got, want := unmapped, []MappableRange{{0x2000, 0x3000}}; !reflect.DeepEqual(got, want) { + t.Errorf("RemoveMapping: got %+v, wanted %+v", got, want) + } + + // Mappings: + // [0x10000, 0x12000) writable => [0x1000, 0x3000) + // [0x21000, 0x22000) readonly => [0x3000, 0x4000) + t.Log(&set) + + // Unmap should fail since writable bit doesn't match. + unmapped = set.RemoveMapping(ms, usermem.AddrRange{0x10000, 0x12000}, 0x1000, false) + if len(unmapped) != 0 { + t.Errorf("RemoveMapping: got %+v, wanted []", unmapped) + } + + unmapped = set.RemoveMapping(ms, usermem.AddrRange{0x10000, 0x12000}, 0x1000, true) + if got, want := unmapped, []MappableRange{{0x1000, 0x2000}}; !reflect.DeepEqual(got, want) { + t.Errorf("RemoveMapping: got %+v, wanted %+v", got, want) + } + + // Mappings: + // [0x21000, 0x22000) readonly => [0x3000, 0x4000) + t.Log(&set) + + unmapped = set.RemoveMapping(ms, usermem.AddrRange{0x21000, 0x22000}, 0x3000, false) + if got, want := unmapped, []MappableRange{{0x3000, 0x4000}}; !reflect.DeepEqual(got, want) { + t.Errorf("RemoveMapping: got %+v, wanted %+v", got, want) + } +} diff --git a/pkg/sentry/memmap/memmap.go b/pkg/sentry/memmap/memmap.go new file mode 100644 index 000000000..c6db9fc8f --- /dev/null +++ b/pkg/sentry/memmap/memmap.go @@ -0,0 +1,363 @@ +// Copyright 2018 The gVisor Authors. +// +// 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. + +// Package memmap defines semantics for memory mappings. +package memmap + +import ( + "fmt" + + "gvisor.dev/gvisor/pkg/context" + "gvisor.dev/gvisor/pkg/sentry/platform" + "gvisor.dev/gvisor/pkg/usermem" +) + +// Mappable represents a memory-mappable object, a mutable mapping from uint64 +// offsets to (platform.File, uint64 File offset) pairs. +// +// See mm/mm.go for Mappable's place in the lock order. +// +// Preconditions: For all Mappable methods, usermem.AddrRanges and +// MappableRanges must be non-empty (Length() != 0), and usermem.Addrs and +// Mappable offsets must be page-aligned. +type Mappable interface { + // AddMapping notifies the Mappable of a mapping from addresses ar in ms to + // offsets [offset, offset+ar.Length()) in this Mappable. + // + // The writable flag indicates whether the backing data for a Mappable can + // be modified through the mapping. Effectively, this means a shared mapping + // where Translate may be called with at.Write == true. This is a property + // established at mapping creation and must remain constant throughout the + // lifetime of the mapping. + // + // Preconditions: offset+ar.Length() does not overflow. + AddMapping(ctx context.Context, ms MappingSpace, ar usermem.AddrRange, offset uint64, writable bool) error + + // RemoveMapping notifies the Mappable of the removal of a mapping from + // addresses ar in ms to offsets [offset, offset+ar.Length()) in this + // Mappable. + // + // Preconditions: offset+ar.Length() does not overflow. The removed mapping + // must exist. writable must match the corresponding call to AddMapping. + RemoveMapping(ctx context.Context, ms MappingSpace, ar usermem.AddrRange, offset uint64, writable bool) + + // CopyMapping notifies the Mappable of an attempt to copy a mapping in ms + // from srcAR to dstAR. For most Mappables, this is equivalent to + // AddMapping. Note that it is possible that srcAR.Length() != dstAR.Length(), + // and also that srcAR.Length() == 0. + // + // CopyMapping is only called when a mapping is copied within a given + // MappingSpace; it is analogous to Linux's vm_operations_struct::mremap. + // + // Preconditions: offset+srcAR.Length() and offset+dstAR.Length() do not + // overflow. The mapping at srcAR must exist. writable must match the + // corresponding call to AddMapping. + CopyMapping(ctx context.Context, ms MappingSpace, srcAR, dstAR usermem.AddrRange, offset uint64, writable bool) error + + // Translate returns the Mappable's current mappings for at least the range + // of offsets specified by required, and at most the range of offsets + // specified by optional. at is the set of access types that may be + // performed using the returned Translations. If not all required offsets + // are translated, it returns a non-nil error explaining why. + // + // Translations are valid until invalidated by a callback to + // MappingSpace.Invalidate or until the caller removes its mapping of the + // translated range. Mappable implementations must ensure that at least one + // reference is held on all pages in a platform.File that may be the result + // of a valid Translation. + // + // Preconditions: required.Length() > 0. optional.IsSupersetOf(required). + // required and optional must be page-aligned. The caller must have + // established a mapping for all of the queried offsets via a previous call + // to AddMapping. The caller is responsible for ensuring that calls to + // Translate synchronize with invalidation. + // + // Postconditions: See CheckTranslateResult. + Translate(ctx context.Context, required, optional MappableRange, at usermem.AccessType) ([]Translation, error) + + // InvalidateUnsavable requests that the Mappable invalidate Translations + // that cannot be preserved across save/restore. + // + // Invariant: InvalidateUnsavable never races with concurrent calls to any + // other Mappable methods. + InvalidateUnsavable(ctx context.Context) error +} + +// Translations are returned by Mappable.Translate. +type Translation struct { + // Source is the translated range in the Mappable. + Source MappableRange + + // File is the mapped file. + File platform.File + + // Offset is the offset into File at which this Translation begins. + Offset uint64 + + // Perms is the set of permissions for which platform.AddressSpace.MapFile + // and platform.AddressSpace.MapInternal on this Translation is permitted. + Perms usermem.AccessType +} + +// FileRange returns the platform.FileRange represented by t. +func (t Translation) FileRange() platform.FileRange { + return platform.FileRange{t.Offset, t.Offset + t.Source.Length()} +} + +// CheckTranslateResult returns an error if (ts, terr) does not satisfy all +// postconditions for Mappable.Translate(required, optional, at). +// +// Preconditions: As for Mappable.Translate. +func CheckTranslateResult(required, optional MappableRange, at usermem.AccessType, ts []Translation, terr error) error { + // Verify that the inputs to Mappable.Translate were valid. + if !required.WellFormed() || required.Length() <= 0 { + panic(fmt.Sprintf("invalid required range: %v", required)) + } + if !usermem.Addr(required.Start).IsPageAligned() || !usermem.Addr(required.End).IsPageAligned() { + panic(fmt.Sprintf("unaligned required range: %v", required)) + } + if !optional.IsSupersetOf(required) { + panic(fmt.Sprintf("optional range %v is not a superset of required range %v", optional, required)) + } + if !usermem.Addr(optional.Start).IsPageAligned() || !usermem.Addr(optional.End).IsPageAligned() { + panic(fmt.Sprintf("unaligned optional range: %v", optional)) + } + + // The first Translation must include required.Start. + if len(ts) != 0 && !ts[0].Source.Contains(required.Start) { + return fmt.Errorf("first Translation %+v does not cover start of required range %v", ts[0], required) + } + for i, t := range ts { + if !t.Source.WellFormed() || t.Source.Length() <= 0 { + return fmt.Errorf("Translation %+v has invalid Source", t) + } + if !usermem.Addr(t.Source.Start).IsPageAligned() || !usermem.Addr(t.Source.End).IsPageAligned() { + return fmt.Errorf("Translation %+v has unaligned Source", t) + } + if t.File == nil { + return fmt.Errorf("Translation %+v has nil File", t) + } + if !usermem.Addr(t.Offset).IsPageAligned() { + return fmt.Errorf("Translation %+v has unaligned Offset", t) + } + // Translations must be contiguous and in increasing order of + // Translation.Source. + if i > 0 && ts[i-1].Source.End != t.Source.Start { + return fmt.Errorf("Translations %+v and %+v are not contiguous", ts[i-1], t) + } + // At least part of each Translation must be required. + if t.Source.Intersect(required).Length() == 0 { + return fmt.Errorf("Translation %+v lies entirely outside required range %v", t, required) + } + // Translations must be constrained to the optional range. + if !optional.IsSupersetOf(t.Source) { + return fmt.Errorf("Translation %+v lies outside optional range %v", t, optional) + } + // Each Translation must permit a superset of requested accesses. + if !t.Perms.SupersetOf(at) { + return fmt.Errorf("Translation %+v does not permit all requested access types %v", t, at) + } + } + // If the set of Translations does not cover the entire required range, + // Translate must return a non-nil error explaining why. + if terr == nil { + if len(ts) == 0 { + return fmt.Errorf("no Translations and no error") + } + if t := ts[len(ts)-1]; !t.Source.Contains(required.End - 1) { + return fmt.Errorf("last Translation %+v does not reach end of required range %v, but Translate returned no error", t, required) + } + } + return nil +} + +// BusError may be returned by implementations of Mappable.Translate for errors +// that should result in SIGBUS delivery if they cause application page fault +// handling to fail. +type BusError struct { + // Err is the original error. + Err error +} + +// Error implements error.Error. +func (b *BusError) Error() string { + return fmt.Sprintf("BusError: %v", b.Err.Error()) +} + +// MappableRange represents a range of uint64 offsets into a Mappable. +// +// type MappableRange <generated using go_generics> + +// String implements fmt.Stringer.String. +func (mr MappableRange) String() string { + return fmt.Sprintf("[%#x, %#x)", mr.Start, mr.End) +} + +// MappingSpace represents a mutable mapping from usermem.Addrs to (Mappable, +// uint64 offset) pairs. +type MappingSpace interface { + // Invalidate is called to notify the MappingSpace that values returned by + // previous calls to Mappable.Translate for offsets mapped by addresses in + // ar are no longer valid. + // + // Invalidate must not take any locks preceding mm.MemoryManager.activeMu + // in the lock order. + // + // Preconditions: ar.Length() != 0. ar must be page-aligned. + Invalidate(ar usermem.AddrRange, opts InvalidateOpts) +} + +// InvalidateOpts holds options to MappingSpace.Invalidate. +type InvalidateOpts struct { + // InvalidatePrivate is true if private pages in the invalidated region + // should also be discarded, causing their data to be lost. + InvalidatePrivate bool +} + +// MappingIdentity controls the lifetime of a Mappable, and provides +// information about the Mappable for /proc/[pid]/maps. It is distinct from +// Mappable because all Mappables that are coherent must compare equal to +// support the implementation of shared futexes, but different +// MappingIdentities may represent the same Mappable, in the same way that +// multiple fs.Files may represent the same fs.Inode. (This similarity is not +// coincidental; fs.File implements MappingIdentity, and some +// fs.InodeOperations implement Mappable.) +type MappingIdentity interface { + // IncRef increments the MappingIdentity's reference count. + IncRef() + + // DecRef decrements the MappingIdentity's reference count. + DecRef() + + // MappedName returns the application-visible name shown in + // /proc/[pid]/maps. + MappedName(ctx context.Context) string + + // DeviceID returns the device number shown in /proc/[pid]/maps. + DeviceID() uint64 + + // InodeID returns the inode number shown in /proc/[pid]/maps. + InodeID() uint64 + + // Msync has the same semantics as fs.FileOperations.Fsync(ctx, + // int64(mr.Start), int64(mr.End-1), fs.SyncData). + // (fs.FileOperations.Fsync() takes an inclusive end, but mr.End is + // exclusive, hence mr.End-1.) It is defined rather than Fsync so that + // implementors don't need to depend on the fs package for fs.SyncType. + Msync(ctx context.Context, mr MappableRange) error +} + +// MLockMode specifies the memory locking behavior of a memory mapping. +type MLockMode int + +// Note that the ordering of MLockModes is significant; see +// mm.MemoryManager.defMLockMode. +const ( + // MLockNone specifies that a mapping has no memory locking behavior. + // + // This must be the zero value for MLockMode. + MLockNone MLockMode = iota + + // MLockEager specifies that a mapping is memory-locked, as by mlock() or + // similar. Pages in the mapping should be made, and kept, resident in + // physical memory as soon as possible. + // + // As of this writing, MLockEager does not cause memory-locking to be + // requested from the host; it only affects the sentry's memory management + // behavior. + // + // MLockEager is analogous to Linux's VM_LOCKED. + MLockEager + + // MLockLazy specifies that a mapping is memory-locked, as by mlock() or + // similar. Pages in the mapping should be kept resident in physical memory + // once they have been made resident due to e.g. a page fault. + // + // As of this writing, MLockLazy does not cause memory-locking to be + // requested from the host; in fact, it has virtually no effect, except for + // interactions between mlocked pages and other syscalls. + // + // MLockLazy is analogous to Linux's VM_LOCKED | VM_LOCKONFAULT. + MLockLazy +) + +// MMapOpts specifies a request to create a memory mapping. +type MMapOpts struct { + // Length is the length of the mapping. + Length uint64 + + // MappingIdentity controls the lifetime of Mappable, and provides + // properties of the mapping shown in /proc/[pid]/maps. If MMapOpts is used + // to successfully create a memory mapping, a reference is taken on + // MappingIdentity. + MappingIdentity MappingIdentity + + // Mappable is the Mappable to be mapped. If Mappable is nil, the mapping + // is anonymous. If Mappable is not nil, it must remain valid as long as a + // reference is held on MappingIdentity. + Mappable Mappable + + // Offset is the offset into Mappable to map. If Mappable is nil, Offset is + // ignored. + Offset uint64 + + // Addr is the suggested address for the mapping. + Addr usermem.Addr + + // Fixed specifies whether this is a fixed mapping (it must be located at + // Addr). + Fixed bool + + // Unmap specifies whether existing mappings in the range being mapped may + // be replaced. If Unmap is true, Fixed must be true. + Unmap bool + + // If Map32Bit is true, all addresses in the created mapping must fit in a + // 32-bit integer. (Note that the "end address" of the mapping, i.e. the + // address of the first byte *after* the mapping, need not fit in a 32-bit + // integer.) Map32Bit is ignored if Fixed is true. + Map32Bit bool + + // Perms is the set of permissions to the applied to this mapping. + Perms usermem.AccessType + + // MaxPerms limits the set of permissions that may ever apply to this + // mapping. If Mappable is not nil, all memmap.Translations returned by + // Mappable.Translate must support all accesses in MaxPerms. + // + // Preconditions: MaxAccessType should be an effective AccessType, as + // access cannot be limited beyond effective AccessTypes. + MaxPerms usermem.AccessType + + // Private is true if writes to the mapping should be propagated to a copy + // that is exclusive to the MemoryManager. + Private bool + + // GrowsDown is true if the mapping should be automatically expanded + // downward on guard page faults. + GrowsDown bool + + // Precommit is true if the platform should eagerly commit resources to the + // mapping (see platform.AddressSpace.MapFile). + Precommit bool + + // MLockMode specifies the memory locking behavior of the mapping. + MLockMode MLockMode + + // Hint is the name used for the mapping in /proc/[pid]/maps. If Hint is + // empty, MappingIdentity.MappedName() will be used instead. + // + // TODO(jamieliu): Replace entirely with MappingIdentity? + Hint string +} |