Skip to content

Commit c14adb5

Browse files
nixprimegvisor-bot
authored andcommitted
Check for fatal signals and reset watchdog during MM.mapASLocked().
PiperOrigin-RevId: 772512410
1 parent ccbefce commit c14adb5

File tree

9 files changed

+70
-34
lines changed

9 files changed

+70
-34
lines changed

pkg/context/context.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ type Blocker interface {
4343
// Interrupted notes whether this context is Interrupted.
4444
Interrupted() bool
4545

46+
// Killed returns true if this context is interrupted by a fatal signal.
47+
Killed() bool
48+
4649
// BlockOn blocks until one of the previously registered events occurs,
4750
// or some external interrupt (cancellation).
4851
//
@@ -94,6 +97,11 @@ func (nt *NoTask) Interrupted() bool {
9497
return nt.cancel != nil && len(nt.cancel) > 0
9598
}
9699

100+
// Killed implements Blocker.Killed.
101+
func (nt *NoTask) Killed() bool {
102+
return false
103+
}
104+
97105
// Block implements Blocker.Block.
98106
func (nt *NoTask) Block(C <-chan struct{}) error {
99107
if nt.cancel == nil {

pkg/sentry/kernel/pending_signals.go

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package kernel
1616

1717
import (
1818
"gvisor.dev/gvisor/pkg/abi/linux"
19+
"gvisor.dev/gvisor/pkg/atomicbitops"
1920
"gvisor.dev/gvisor/pkg/bits"
2021
)
2122

@@ -35,8 +36,7 @@ const (
3536
)
3637

3738
// pendingSignals holds a collection of pending signals. The zero value of
38-
// pendingSignals is a valid empty collection. pendingSignals is thread-unsafe;
39-
// users must provide synchronization.
39+
// pendingSignals is a valid empty collection.
4040
//
4141
// +stateify savable
4242
type pendingSignals struct {
@@ -45,11 +45,19 @@ type pendingSignals struct {
4545
// Note that signals is zero-indexed, but signal 1 is the first valid
4646
// signal, so signals[0] contains signals with signo 1 etc. This offset is
4747
// usually handled by using Signal.index().
48+
//
49+
// signals is protected by the signal mutex for the containing Task or
50+
// ThreadGroup.
4851
signals [linux.SignalMaximum]pendingSignalQueue `state:".([]savedPendingSignal)"`
4952

5053
// Bit i of pendingSet is set iff there is at least one signal with signo
5154
// i+1 pending.
52-
pendingSet linux.SignalSet `state:"manual"`
55+
//
56+
// pendingSet is accessed using atomic memory operations, and is protected
57+
// by the signal mutex (such that reading pendingSet is safe if either the
58+
// signal mutex is locked or if atomic memory operations are used, while
59+
// writing pendingSet requires both).
60+
pendingSet atomicbitops.Uint64 `state:"manual"`
5361
}
5462

5563
// pendingSignalQueue holds a pendingSignalList for a single signal number.
@@ -86,7 +94,7 @@ func (p *pendingSignals) enqueue(info *linux.SignalInfo, timer *IntervalTimer) b
8694
}
8795
q.pendingSignalList.PushBack(&pendingSignal{SignalInfo: info, timer: timer})
8896
q.length++
89-
p.pendingSet |= linux.SignalSetOf(sig)
97+
p.pendingSet.Store(p.pendingSet.RacyLoad() | uint64(linux.SignalSetOf(sig)))
9098
return true
9199
}
92100

@@ -103,7 +111,7 @@ func (p *pendingSignals) dequeue(mask linux.SignalSet) *linux.SignalInfo {
103111
// process, POSIX leaves it unspecified which is delivered first. Linux,
104112
// like many other implementations, gives priority to standard signals in
105113
// this case." - signal(7)
106-
lowestPendingUnblockedBit := bits.TrailingZeros64(uint64(p.pendingSet &^ mask))
114+
lowestPendingUnblockedBit := bits.TrailingZeros64(p.pendingSet.RacyLoad() &^ uint64(mask))
107115
if lowestPendingUnblockedBit >= linux.SignalMaximum {
108116
return nil
109117
}
@@ -119,7 +127,7 @@ func (p *pendingSignals) dequeueSpecific(sig linux.Signal) *linux.SignalInfo {
119127
q.pendingSignalList.Remove(ps)
120128
q.length--
121129
if q.length == 0 {
122-
p.pendingSet &^= linux.SignalSetOf(sig)
130+
p.pendingSet.Store(p.pendingSet.RacyLoad() &^ uint64(linux.SignalSetOf(sig)))
123131
}
124132
if ps.timer != nil {
125133
ps.timer.updateDequeuedSignalLocked(ps.SignalInfo)
@@ -137,5 +145,5 @@ func (p *pendingSignals) discardSpecific(sig linux.Signal) {
137145
}
138146
q.pendingSignalList.Reset()
139147
q.length = 0
140-
p.pendingSet &^= linux.SignalSetOf(sig)
148+
p.pendingSet.Store(p.pendingSet.RacyLoad() &^ uint64(linux.SignalSetOf(sig)))
141149
}

pkg/sentry/kernel/task_block.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ func (t *Task) Interrupted() bool {
234234
}
235235
// Indicate that t's task goroutine is still responsive (i.e. reset the
236236
// watchdog timer).
237-
t.accountTaskGoroutineRunning()
237+
t.touchGostateTime()
238238
return false
239239
}
240240

pkg/sentry/kernel/task_exit.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,19 +100,28 @@ func (t *Task) killLocked() {
100100
t.interrupt()
101101
}
102102

103+
// Killed implements context.Blocker.Killed.
104+
func (t *Task) Killed() bool {
105+
if t.killed() {
106+
return true
107+
}
108+
// Indicate that t's task goroutine is still responsive (i.e. reset the
109+
// watchdog timer).
110+
t.touchGostateTime()
111+
return false
112+
}
113+
103114
// killed returns true if t has a SIGKILL pending. killed is analogous to
104115
// Linux's fatal_signal_pending().
105116
//
106117
// Preconditions: The caller must be running on the task goroutine.
107118
func (t *Task) killed() bool {
108-
t.tg.signalHandlers.mu.Lock()
109-
defer t.tg.signalHandlers.mu.Unlock()
110-
return t.killedLocked()
119+
return linux.SignalSet(t.pendingSignals.pendingSet.Load())&linux.SignalSetOf(linux.SIGKILL) != 0
111120
}
112121

113122
// Preconditions: The signal mutex must be locked.
114123
func (t *Task) killedLocked() bool {
115-
return t.pendingSignals.pendingSet&linux.SignalSetOf(linux.SIGKILL) != 0
124+
return linux.SignalSet(t.pendingSignals.pendingSet.RacyLoad())&linux.SignalSetOf(linux.SIGKILL) != 0
116125
}
117126

118127
// PrepareExit indicates an exit with the given status.

pkg/sentry/kernel/task_sched.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -116,14 +116,6 @@ func (t *Task) accountTaskGoroutineLeave(state TaskGoroutineState) {
116116
t.gostateSeq.EndWrite()
117117
}
118118

119-
// Preconditions: The caller must be running on the task goroutine.
120-
func (t *Task) accountTaskGoroutineRunning() {
121-
if oldState := t.TaskGoroutineState(); oldState != TaskGoroutineRunningSys {
122-
panic(fmt.Sprintf("Task goroutine in state %v (expected %v)", oldState, TaskGoroutineRunningSys))
123-
}
124-
t.touchGostateTime()
125-
}
126-
127119
// Preconditions: The caller must be running on the task goroutine.
128120
func (t *Task) touchGostateTime() {
129121
t.gostateTime.Store(t.k.cpuClock.Load())

pkg/sentry/kernel/task_signals.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,7 @@ func (tg *ThreadGroup) discardSpecificLocked(sig linux.Signal) {
146146

147147
// PendingSignals returns the set of pending signals.
148148
func (t *Task) PendingSignals() linux.SignalSet {
149-
sh := t.tg.signalLock()
150-
defer sh.mu.Unlock()
151-
return t.pendingSignals.pendingSet | t.tg.pendingSignals.pendingSet
149+
return linux.SignalSet(t.pendingSignals.pendingSet.Load() | t.tg.pendingSignals.pendingSet.Load())
152150
}
153151

154152
// deliverSignal delivers the given signal and returns the following run state.
@@ -612,7 +610,7 @@ func (t *Task) setSignalMaskLocked(mask linux.SignalSet) {
612610
// signal, but will no longer do so as a result of its new signal mask, so
613611
// we have to pick a replacement.
614612
blocked := mask &^ oldMask
615-
blockedGroupPending := blocked & t.tg.pendingSignals.pendingSet
613+
blockedGroupPending := blocked & linux.SignalSet(t.tg.pendingSignals.pendingSet.RacyLoad())
616614
if blockedGroupPending != 0 && t.interrupted() {
617615
linux.ForEachSignal(blockedGroupPending, func(sig linux.Signal) {
618616
if nt := t.tg.findSignalReceiverLocked(sig); nt != nil {
@@ -626,7 +624,7 @@ func (t *Task) setSignalMaskLocked(mask linux.SignalSet) {
626624
// the old mask, and at least one such signal is pending, we may now need
627625
// to handle that signal.
628626
unblocked := oldMask &^ mask
629-
unblockedPending := unblocked & (t.pendingSignals.pendingSet | t.tg.pendingSignals.pendingSet)
627+
unblockedPending := unblocked & linux.SignalSet(t.pendingSignals.pendingSet.RacyLoad()|t.tg.pendingSignals.pendingSet.RacyLoad())
630628
if unblockedPending != 0 {
631629
t.interruptSelf()
632630
}

pkg/sentry/mm/address_space.go

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"fmt"
1919

2020
"gvisor.dev/gvisor/pkg/context"
21+
"gvisor.dev/gvisor/pkg/errors/linuxerr"
2122
"gvisor.dev/gvisor/pkg/hostarch"
2223
"gvisor.dev/gvisor/pkg/sentry/memmap"
2324
"gvisor.dev/gvisor/pkg/sentry/pgalloc"
@@ -172,7 +173,7 @@ func (mm *MemoryManager) Deactivate() {
172173
// - ar.Length() != 0.
173174
// - ar must be page-aligned.
174175
// - pseg == mm.pmas.LowerBoundSegment(ar.Start).
175-
func (mm *MemoryManager) mapASLocked(pseg pmaIterator, ar hostarch.AddrRange, platformEffect memmap.MMapPlatformEffect) error {
176+
func (mm *MemoryManager) mapASLocked(ctx context.Context, pseg pmaIterator, ar hostarch.AddrRange, platformEffect memmap.MMapPlatformEffect) error {
176177
// By default, map entire pmas at a time, under the assumption that there
177178
// is no cost to mapping more of a pma than necessary.
178179
mapAR := hostarch.AddrRange{0, ^hostarch.Addr(hostarch.PageSize - 1)}
@@ -217,8 +218,28 @@ func (mm *MemoryManager) mapASLocked(pseg pmaIterator, ar hostarch.AddrRange, pl
217218
perms.Write = false
218219
}
219220
if perms.Any() { // MapFile precondition
220-
if err := mm.as.MapFile(pmaMapAR.Start, pma.file, pseg.fileRangeOf(pmaMapAR), perms, platformEffect == memmap.PlatformEffectCommit); err != nil {
221-
return err
221+
// If the length of the mapping exceeds singleMapThreshold, call
222+
// AddressSpace.MapFile() on singleMapThreshold-aligned chunks so
223+
// we can check ctx.Killed() reasonably frequently.
224+
const singleMapThreshold = 1 << 30
225+
if pmaMapAR.Length() <= singleMapThreshold {
226+
if err := mm.as.MapFile(pmaMapAR.Start, pma.file, pseg.fileRangeOf(pmaMapAR), perms, platformEffect == memmap.PlatformEffectCommit); err != nil {
227+
return err
228+
}
229+
if ctx.Killed() {
230+
return linuxerr.EINTR
231+
}
232+
} else {
233+
for windowStart := pmaMapAR.Start &^ (singleMapThreshold - 1); windowStart < pmaMapAR.End; windowStart += singleMapThreshold {
234+
windowAR := hostarch.AddrRange{windowStart, windowStart + singleMapThreshold}
235+
thisMapAR := pmaMapAR.Intersect(windowAR)
236+
if err := mm.as.MapFile(thisMapAR.Start, pma.file, pseg.fileRangeOf(thisMapAR), perms, platformEffect == memmap.PlatformEffectCommit); err != nil {
237+
return err
238+
}
239+
if ctx.Killed() {
240+
return linuxerr.EINTR
241+
}
242+
}
222243
}
223244
}
224245
pseg = pseg.NextSegment()

pkg/sentry/mm/io.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,7 @@ func (mm *MemoryManager) handleASIOFault(ctx context.Context, addr hostarch.Addr
514514
// anymore.
515515
mm.activeMu.DowngradeLock()
516516

517-
err = mm.mapASLocked(pseg, ar, memmap.PlatformEffectDefault)
517+
err = mm.mapASLocked(ctx, pseg, ar, memmap.PlatformEffectDefault)
518518
mm.activeMu.RUnlock()
519519
return translateIOError(ctx, err)
520520
}

pkg/sentry/mm/syscalls.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ func (mm *MemoryManager) HandleUserFault(ctx context.Context, addr hostarch.Addr
6666
mm.activeMu.DowngradeLock()
6767

6868
// Map the faulted page into the active AddressSpace.
69-
err = mm.mapASLocked(pseg, ar, memmap.PlatformEffectDefault)
69+
err = mm.mapASLocked(ctx, pseg, ar, memmap.PlatformEffectDefault)
7070
mm.activeMu.RUnlock()
7171
return err
7272
}
@@ -201,7 +201,7 @@ func (mm *MemoryManager) populateVMA(ctx context.Context, vseg vmaIterator, ar h
201201
// Downgrade to a read-lock on activeMu since we don't need to mutate pmas
202202
// anymore.
203203
mm.activeMu.DowngradeLock()
204-
err = mm.mapASLocked(pseg, ar, platformEffect)
204+
err = mm.mapASLocked(ctx, pseg, ar, platformEffect)
205205
mm.activeMu.RUnlock()
206206
return err
207207
}
@@ -250,7 +250,7 @@ func (mm *MemoryManager) populateVMAAndUnlock(ctx context.Context, vseg vmaItera
250250

251251
// As above, errors are silently ignored.
252252
mm.activeMu.DowngradeLock()
253-
mm.mapASLocked(pseg, ar, platformEffect)
253+
mm.mapASLocked(ctx, pseg, ar, platformEffect)
254254
mm.activeMu.RUnlock()
255255
}
256256

@@ -930,7 +930,7 @@ func (mm *MemoryManager) MLock(ctx context.Context, addr hostarch.Addr, length u
930930
mm.mappingMu.RUnlock()
931931
if mm.as != nil {
932932
mm.activeMu.DowngradeLock()
933-
err := mm.mapASLocked(mm.pmas.LowerBoundSegment(ar.Start), ar, memmap.PlatformEffectCommit)
933+
err := mm.mapASLocked(ctx, mm.pmas.LowerBoundSegment(ar.Start), ar, memmap.PlatformEffectCommit)
934934
mm.activeMu.RUnlock()
935935
if err != nil {
936936
return err
@@ -1014,7 +1014,7 @@ func (mm *MemoryManager) MLockAll(ctx context.Context, opts MLockAllOpts) error
10141014
mm.mappingMu.RUnlock()
10151015
if mm.as != nil {
10161016
mm.activeMu.DowngradeLock()
1017-
mm.mapASLocked(mm.pmas.FirstSegment(), mm.applicationAddrRange(), memmap.PlatformEffectCommit)
1017+
mm.mapASLocked(ctx, mm.pmas.FirstSegment(), mm.applicationAddrRange(), memmap.PlatformEffectCommit)
10181018
mm.activeMu.RUnlock()
10191019
} else {
10201020
mm.activeMu.Unlock()

0 commit comments

Comments
 (0)