Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkg/sentry/fsimpl/testutil/kernel.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func Boot() (*kernel.Kernel, error) {
// Create timekeeper.
tk := kernel.NewTimekeeper()
params := kernel.NewVDSOParamPage(k.MemoryFile(), vdso.ParamPage.FileRange())
tk.SetClocks(time.NewCalibratedClocks(), params)
tk.SetClocks(time.NewCalibratedClocks(false), params)

creds := auth.NewRootCredentials(auth.NewRootUserNamespace())

Expand Down
9 changes: 9 additions & 0 deletions pkg/sentry/kernel/kernel.go
Original file line number Diff line number Diff line change
Expand Up @@ -1906,6 +1906,15 @@ func (k *Kernel) MonotonicClock() ktime.SampledClock {
return k.timekeeper.monotonicClock
}

// MonotonicRawClock returns the system CLOCK_MONOTONIC_RAW clock. When it is
// not enabled as a distinct clock this is the same as MonotonicClock.
func (k *Kernel) MonotonicRawClock() ktime.SampledClock {
if k.timekeeper.monotonicRawClock != nil {
return k.timekeeper.monotonicRawClock
}
return k.timekeeper.monotonicClock
}

// Syslog returns the syslog.
func (k *Kernel) Syslog() *syslog {
return &k.syslog
Expand Down
49 changes: 40 additions & 9 deletions pkg/sentry/kernel/timekeeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ type Timekeeper struct {
// monotonicClock is a ktime.Clock based on timekeeper's Monotonic.
monotonicClock *timekeeperClock

// monotonicRawClock is a ktime.Clock based on timekeeper's MonotonicRaw.
// It is non-nil only when the clock source tracks a distinct
// CLOCK_MONOTONIC_RAW; otherwise CLOCK_MONOTONIC_RAW aliases
// CLOCK_MONOTONIC. It is derived by SetClocks, anew on restore.
monotonicRawClock *timekeeperClock `state:"nosave"`

// bootTime is the realtime when the system "booted". i.e., when
// SetClocks was called in the initial (not restored) run.
bootTime ktime.Time
Expand Down Expand Up @@ -158,6 +164,14 @@ func (t *Timekeeper) SetClocks(c sentrytime.Clocks, params *VDSOParamPage) {

t.clocks = c

// Serve CLOCK_MONOTONIC_RAW as a distinct clock iff the clock source
// tracks it (see NewCalibratedClocks). Deriving this from the source keeps
// boot and restore coherent: a restored sandbox follows its current
// configuration, not the checkpointed one.
if _, err := c.GetTime(sentrytime.MonotonicRaw); err == nil {
t.monotonicRawClock = &timekeeperClock{tk: t, c: sentrytime.MonotonicRaw}
}

// Compute the offset of the monotonic clock from the base Clocks.
//
// In a fresh (not restored) sentry, monotonic time starts at zero.
Expand Down Expand Up @@ -213,20 +227,31 @@ func (t *Timekeeper) update(parked bool) {
// Call Update within a Write block to prevent the VDSO from using the old
// params between Update and Write.
if err := t.params.Write(func() vdsoParams {
monotonicParams, monotonicOk, realtimeParams, realtimeOk := t.clocks.Update(parked)
res := t.clocks.Update(parked)

var p vdsoParams
if monotonicOk {
if res.MonotonicOk {
p.monotonicReady = 1
p.monotonicBaseCycles = int64(monotonicParams.BaseCycles)
p.monotonicBaseRef = int64(monotonicParams.BaseRef) + t.monotonicOffset
p.monotonicFrequency = monotonicParams.Frequency
p.monotonicBaseCycles = int64(res.Monotonic.BaseCycles)
p.monotonicBaseRef = int64(res.Monotonic.BaseRef) + t.monotonicOffset
p.monotonicFrequency = res.Monotonic.Frequency
}
if realtimeOk {
if res.RealtimeOk {
p.realtimeReady = 1
p.realtimeBaseCycles = int64(realtimeParams.BaseCycles)
p.realtimeBaseRef = int64(realtimeParams.BaseRef)
p.realtimeFrequency = realtimeParams.Frequency
p.realtimeBaseCycles = int64(res.Realtime.BaseCycles)
p.realtimeBaseRef = int64(res.Realtime.BaseRef)
p.realtimeFrequency = res.Realtime.Frequency
}
if t.monotonicRawClock != nil {
// Raw tracks absolute host CLOCK_MONOTONIC_RAW, so unlike
// monotonic its base ref is published without monotonicOffset.
p.monotonicRawEnabled = 1
if res.MonotonicRawOk {
p.monotonicRawReady = 1
p.monotonicRawBaseCycles = int64(res.MonotonicRaw.BaseCycles)
p.monotonicRawBaseRef = int64(res.MonotonicRaw.BaseRef)
p.monotonicRawFrequency = res.MonotonicRaw.Frequency
}
}
return p
}); err != nil {
Expand Down Expand Up @@ -397,6 +422,12 @@ func (t *Timekeeper) GetTime(c sentrytime.ClockID) (int64, error) {
}
<-t.restored
}
if c == sentrytime.MonotonicRaw && t.monotonicRawClock == nil {
// Alias of CLOCK_MONOTONIC when the clock source does not track a
// distinct CLOCK_MONOTONIC_RAW. This also covers raw clock users
// restored from a checkpoint whose source tracked it.
c = sentrytime.Monotonic
}

// update the calibration if needed and keep the timekeeper calibrated
// during the read
Expand Down
58 changes: 53 additions & 5 deletions pkg/sentry/kernel/timekeeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ import (
// mockClocks is a sentrytime.Clocks that simply returns the times in the
// struct.
type mockClocks struct {
monotonic int64
realtime int64
monotonic int64
realtime int64
monotonicRaw int64
monotonicRawEnabled bool
}

// Update implements sentrytime.Clocks.Update. It does nothing.
func (*mockClocks) Update(parked bool) (monotonicParams sentrytime.Parameters, monotonicOk bool, realtimeParam sentrytime.Parameters, realtimeOk bool) {
return
func (*mockClocks) Update(parked bool) sentrytime.UpdateResult {
return sentrytime.UpdateResult{}
}

// GetTime implements sentrytime.Clocks.GetTime.
Expand All @@ -44,6 +46,11 @@ func (c *mockClocks) GetTime(id sentrytime.ClockID) (int64, error) {
return c.monotonic, nil
case sentrytime.Realtime:
return c.realtime, nil
case sentrytime.MonotonicRaw:
if c.monotonicRawEnabled {
return c.monotonicRaw, nil
}
return 0, linuxerr.EINVAL
default:
return 0, linuxerr.EINVAL
}
Expand All @@ -64,7 +71,7 @@ func stateTestClocklessTimekeeper(tb testing.TB) (*Timekeeper, *VDSOParamPage) {

func stateTestTimekeeper(tb testing.TB) *Timekeeper {
t, params := stateTestClocklessTimekeeper(tb)
t.SetClocks(sentrytime.NewCalibratedClocks(), params)
t.SetClocks(sentrytime.NewCalibratedClocks(false), params)
return t
}

Expand Down Expand Up @@ -153,3 +160,44 @@ func TestTimekeeperMonotonicJumpBackwards(t *testing.T) {
t.Errorf("GetTime got %d want 100000", now)
}
}

// TestTimekeeperMonotonicRawEnabled tests that when the clock source tracks a
// distinct CLOCK_MONOTONIC_RAW, GetTime exposes it directly (absolute, not
// starting at zero), while CLOCK_MONOTONIC is unaffected.
func TestTimekeeperMonotonicRawEnabled(t *testing.T) {
c := &mockClocks{
monotonic: 100000,
monotonicRaw: 999999,
monotonicRawEnabled: true,
}

tk, params := stateTestClocklessTimekeeper(t)
tk.SetClocks(c, params)
defer tk.Destroy()

// Monotonic is unaffected: still starts at zero.
if now, err := tk.GetTime(sentrytime.Monotonic); err != nil || now != 0 {
t.Errorf("GetTime(Monotonic) got (%d, %v) want (0, nil)", now, err)
}
// MonotonicRaw exposes the raw source's absolute value.
if now, err := tk.GetTime(sentrytime.MonotonicRaw); err != nil || now != 999999 {
t.Errorf("GetTime(MonotonicRaw) got (%d, %v) want (999999, nil)", now, err)
}
}

// TestTimekeeperMonotonicRawDisabled tests that when the clock source does not
// track CLOCK_MONOTONIC_RAW, it aliases CLOCK_MONOTONIC.
func TestTimekeeperMonotonicRawDisabled(t *testing.T) {
c := &mockClocks{
monotonic: 100000,
}

tk, params := stateTestClocklessTimekeeper(t)
tk.SetClocks(c, params)
defer tk.Destroy()

// MonotonicRaw aliases Monotonic: both start at zero.
if now, err := tk.GetTime(sentrytime.MonotonicRaw); err != nil || now != 0 {
t.Errorf("GetTime(MonotonicRaw) got (%d, %v) want (0, nil)", now, err)
}
}
10 changes: 10 additions & 0 deletions pkg/sentry/kernel/vdso.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ type vdsoParams struct {
realtimeBaseCycles int64
realtimeBaseRef int64
realtimeFrequency uint64

// monotonicRawEnabled, when non-zero, tells the VDSO that
// CLOCK_MONOTONIC_RAW is a distinct clock (described by the fields below)
// rather than an alias of CLOCK_MONOTONIC. monotonicRawReady is set only
// once it has been calibrated; until then the VDSO falls back to a syscall.
monotonicRawEnabled uint64
monotonicRawReady uint64
monotonicRawBaseCycles int64
monotonicRawBaseRef int64
monotonicRawFrequency uint64
}

// VDSOParamPage manages a VDSO parameter page.
Expand Down
5 changes: 3 additions & 2 deletions pkg/sentry/syscalls/linux/sys_time.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,10 @@ func getClock(t *kernel.Task, clockID int32) (ktime.Clock, error) {
switch clockID {
case linux.CLOCK_REALTIME, linux.CLOCK_REALTIME_COARSE:
return t.Kernel().RealtimeClock(), nil
case linux.CLOCK_MONOTONIC_RAW:
return t.Kernel().MonotonicRawClock(), nil
case linux.CLOCK_MONOTONIC, linux.CLOCK_MONOTONIC_COARSE,
linux.CLOCK_MONOTONIC_RAW, linux.CLOCK_BOOTTIME:
// CLOCK_MONOTONIC approximates CLOCK_MONOTONIC_RAW.
linux.CLOCK_BOOTTIME:
// CLOCK_BOOTTIME is internally mapped to CLOCK_MONOTONIC, as:
// - CLOCK_BOOTTIME should behave as CLOCK_MONOTONIC while also
// including suspend time.
Expand Down
35 changes: 28 additions & 7 deletions pkg/sentry/time/calibrated_clock.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,22 +234,38 @@ type CalibratedClocks struct {

// realtime is the realtime equivalent of monotonic.
realtime *CalibratedClock

// monotonicRaw tracks the host CLOCK_MONOTONIC_RAW clock. It is nil unless
// enabled via NewCalibratedClocks.
monotonicRaw *CalibratedClock
}

// NewCalibratedClocks creates a CalibratedClocks.
func NewCalibratedClocks() *CalibratedClocks {
return &CalibratedClocks{
// NewCalibratedClocks creates a CalibratedClocks. If monotonicRawEnabled is
// true, host CLOCK_MONOTONIC_RAW is additionally tracked as a distinct clock
// (addressable as MonotonicRaw).
func NewCalibratedClocks(monotonicRawEnabled bool) *CalibratedClocks {
c := &CalibratedClocks{
monotonic: NewCalibratedClock(Monotonic),
realtime: NewCalibratedClock(Realtime),
}
if monotonicRawEnabled {
c.monotonicRaw = NewCalibratedClock(MonotonicRaw)
}
return c
}

// Update implements Clocks.Update.
func (c *CalibratedClocks) Update(parked bool) (Parameters, bool, Parameters, bool) {
monotonicParams, monotonicOk := c.monotonic.Update(parked)
realtimeParams, realtimeOk := c.realtime.Update(parked)
func (c *CalibratedClocks) Update(parked bool) UpdateResult {
var res UpdateResult
res.Monotonic, res.MonotonicOk = c.monotonic.Update(parked)
res.Realtime, res.RealtimeOk = c.realtime.Update(parked)
if c.monotonicRaw != nil {
// Keep the raw clock calibrated and publish its parameters so the VDSO
// can serve CLOCK_MONOTONIC_RAW without a syscall.
res.MonotonicRaw, res.MonotonicRawOk = c.monotonicRaw.Update(parked)
}

return monotonicParams, monotonicOk, realtimeParams, realtimeOk
return res
}

// GetTime implements Clocks.GetTime.
Expand All @@ -259,6 +275,11 @@ func (c *CalibratedClocks) GetTime(id ClockID) (int64, error) {
return c.monotonic.GetTime()
case Realtime:
return c.realtime.GetTime()
case MonotonicRaw:
if c.monotonicRaw != nil {
return c.monotonicRaw.GetTime()
Comment thread
ayushr2 marked this conversation as resolved.
}
return 0, linuxerr.EINVAL
default:
return 0, linuxerr.EINVAL
}
Expand Down
7 changes: 5 additions & 2 deletions pkg/sentry/time/clock_id.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ type ClockID int32

// These are the supported Linux clock identifiers.
const (
Realtime ClockID = iota
Monotonic
Realtime ClockID = 0
Monotonic ClockID = 1
MonotonicRaw ClockID = 4
Comment thread
ayushr2 marked this conversation as resolved.
)

// String implements fmt.Stringer.String.
Expand All @@ -34,6 +35,8 @@ func (c ClockID) String() string {
return "Realtime"
case Monotonic:
return "Monotonic"
case MonotonicRaw:
return "MonotonicRaw"
default:
return strconv.Itoa(int(c))
}
Expand Down
17 changes: 16 additions & 1 deletion pkg/sentry/time/clocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,21 @@

package time

// UpdateResult holds the timekeeping parameters produced by Clocks.Update.
//
// Each *Ok field reports whether the corresponding *Params were successfully
// calibrated and may be published to the VDSO. MonotonicRaw is only populated
// by clock sources that track a distinct CLOCK_MONOTONIC_RAW; otherwise
// MonotonicRawOk is false.
type UpdateResult struct {
Monotonic Parameters
MonotonicOk bool
Realtime Parameters
RealtimeOk bool
MonotonicRaw Parameters
MonotonicRawOk bool
}

// Clocks represents a clock source that contains both a monotonic and realtime
// clock.
type Clocks interface {
Expand All @@ -23,7 +38,7 @@ type Clocks interface {
// Update should be called at approximately ApproxUpdateInterval.
//
// parked indicates that the clock was not read for at least ApproxUpdateInterval
Update(parked bool) (monotonicParams Parameters, monotonicOk bool, realtimeParam Parameters, realtimeOk bool)
Update(parked bool) UpdateResult

// GetTime returns the current time in nanoseconds for the given clock.
//
Expand Down
20 changes: 19 additions & 1 deletion runsc/boot/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,24 @@ func getRootCredentials(spec *specs.Spec, conf *config.Config, userNs *auth.User
return creds
}

// shouldEnableClockMonotonicRaw reports whether CLOCK_MONOTONIC_RAW should be
// exposed as a distinct clock tracking the host's CLOCK_MONOTONIC_RAW, rather
// than aliasing CLOCK_MONOTONIC as it does by default.
//
// This exists for GPU profiling: profilers such as Nsight Systems/CUPTI anchor
// the GPU timeline in the host's CLOCK_MONOTONIC_RAW domain, which drifts from
// CLOCK_MONOTONIC by NTP frequency adjustment. When nvproxy grants
// CapProfiling, the sandbox must therefore serve a CLOCK_MONOTONIC_RAW in that
// same (absolute, unadjusted) domain. It is enabled only in that case; every
// other clock comment in this feature refers back here for the rationale.
func shouldEnableClockMonotonicRaw(spec *specs.Spec, conf *config.Config) bool {
if !specutils.NVProxyEnabled(spec, conf) {
return false
}
caps, err := specutils.NVProxyDriverCapsAllowed(conf)
return err == nil && caps&nvconf.CapProfiling != 0
}

// New initializes a new kernel loader configured by spec.
// New also handles setting up a kernel for restoring a container.
func New(args Args) (*Loader, error) {
Expand Down Expand Up @@ -675,7 +693,7 @@ func New(args Args) (*Loader, error) {
// Create timekeeper.
tk := kernel.NewTimekeeper()
params := kernel.NewVDSOParamPage(l.k.MemoryFile(), vdso.ParamPage.FileRange())
tk.SetClocks(time.NewCalibratedClocks(), params)
tk.SetClocks(time.NewCalibratedClocks(shouldEnableClockMonotonicRaw(args.Spec, args.Conf)), params)

if err := enableStrace(args.Conf); err != nil {
return nil, fmt.Errorf("enabling strace: %w", err)
Expand Down
9 changes: 6 additions & 3 deletions runsc/boot/restore.go
Original file line number Diff line number Diff line change
Expand Up @@ -488,16 +488,19 @@ func (r *restorer) restore(l *Loader) error {
return err
}

// Load the state.
// Load the state. The Timekeeper serves a distinct CLOCK_MONOTONIC_RAW
// iff the clock source tracks it (see shouldEnableClockMonotonicRaw), so a
// restored sandbox follows its current configuration.
clocks := time.NewCalibratedClocks(shouldEnableClockMonotonicRaw(l.root.spec, l.root.conf))
r.timer.Reached("loading kernel")
if r.extractRootFsMode {
if err := l.k.ExtractRootfsUpperLayer(ctx, r.stateFile, r.asyncMFLoader, nil, time.NewCalibratedClocks(), r.rootFsOutputTar); err != nil {
if err := l.k.ExtractRootfsUpperLayer(ctx, r.stateFile, r.asyncMFLoader, nil, clocks, r.rootFsOutputTar); err != nil {
return fmt.Errorf("failed to extract rootfs upper layer: %w", err)
}
r.timer.Reached("rootfs upper layer extracted")
return nil
}
if err := l.k.LoadFrom(ctx, r.stateFile, r.asyncMFLoader, nil, l, time.NewCalibratedClocks(), &vfs.CompleteRestoreOptions{}, r.timer.Fork("kernel load")); err != nil {
if err := l.k.LoadFrom(ctx, r.stateFile, r.asyncMFLoader, nil, l, clocks, &vfs.CompleteRestoreOptions{}, r.timer.Fork("kernel load")); err != nil {
return fmt.Errorf("failed to load kernel: %w", err)
}
r.timer.Reached("kernel loaded")
Expand Down
Loading