Skip to content
Draft
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
44 changes: 43 additions & 1 deletion cmd/gcs-sidecar/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,13 @@ import (
"unsafe"

"github.com/Microsoft/go-winio"
"github.com/Microsoft/go-winio/pkg/etw"
"github.com/Microsoft/go-winio/pkg/etwlogrus"
"github.com/Microsoft/go-winio/pkg/guid"
"github.com/Microsoft/hcsshim/internal/gcs/prot"
shimlog "github.com/Microsoft/hcsshim/internal/log"
"github.com/Microsoft/hcsshim/internal/ot"
logsources "github.com/Microsoft/hcsshim/internal/vm/vmutils/etw"
"github.com/Microsoft/hcsshim/pkg/amdsevsnp"
"github.com/Microsoft/hcsshim/pkg/securitypolicy"
"github.com/sirupsen/logrus"
Expand Down Expand Up @@ -191,6 +195,44 @@ func main() {
}
os.Stderr = logFileHandle

var loggingOptions []func(*securitypolicy.SecurityOptions)
runtimeLogging := sidecar.NewRuntimeLoggingController()
provider, err := etw.NewProvider(logsources.SidecarProviderName, func(
_ guid.GUID,
state etw.ProviderState,
_ etw.Level,
_, _ uint64,
_ uintptr,
) {
switch state {
case etw.ProviderStateEnable:
runtimeLogging.SetCollectionEnabled(true)
case etw.ProviderStateDisable:
runtimeLogging.SetCollectionEnabled(false)
default:
return
}
})
if err != nil {
logrus.WithError(err).Error("failed to register sidecar ETW provider")
} else if hook, err := etwlogrus.NewHookFromProvider(provider); err != nil {
logrus.WithError(err).Error("failed to initialize sidecar ETW hook")
if err := provider.Close(); err != nil {
logrus.WithError(err).Error("failed to close sidecar ETW provider")
}
} else {
runtimeHook := sidecar.NewRuntimeLoggingHook(hook)
defer func() {
runtimeHook.Close()
if err := provider.Close(); err != nil {
logrus.WithError(err).Error("failed to close sidecar ETW provider")
}
}()
runtimeLogging.Attach(runtimeHook)
logrus.AddHook(runtimeHook)
loggingOptions = append(loggingOptions, securitypolicy.WithRuntimeLoggingChanged(runtimeHook.SetAllowed))
}
Comment on lines +198 to +234

chsrv := make(chan error)
go func() {
defer close(chsrv)
Expand Down Expand Up @@ -278,7 +320,7 @@ func main() {
initialEnforcer := &securitypolicy.ClosedDoorSecurityPolicyEnforcer{}

// 3. Create bridge and initializa
brdg := sidecar.NewBridge(shimCon, gcsCon, initialEnforcer, logFileHandle)
brdg := sidecar.NewBridge(shimCon, gcsCon, initialEnforcer, logFileHandle, loggingOptions...)
brdg.AssignHandlers()

// 3. Listen and serve for hcsshim requests.
Expand Down
75 changes: 75 additions & 0 deletions docs/sidecar-gcs-logging.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Sidecar GCS ETW logging

Sidecar GCS emits runtime diagnostics through the
`Microsoft.Virtualization.GCSSidecar` ETW provider
(`5400e7dc-0f0f-553d-19b5-3672635b5f7b`), using the same Logrus ETW hook as
the host shim. Log Forward Service collects these events and forwards them
through the existing Windows logging socket, not the GCS control bridge.

## Policy

The ETW hook buffers a bounded number of pre-policy startup events. It forwards
them only after the guest validates and installs a policy allowing
`allow_runtime_logging` and an ETW collection session is enabled. If policy
denies runtime logging, buffered events are discarded. The existing Log
Forward Service provider policy separately controls collection. There is no
additional forwarding permission.

Example policy excerpt (not a complete policy):

```rego
allow_runtime_logging := true
allowed_log_providers := [
"Microsoft.Virtualization.GCSSidecar",
]
allow_log_provider_dropping := false
log_provider := data.framework.log_provider
```

Existing versioned policy compatibility defaults still apply. Older policies
can allow collection without an explicit provider list.

## Collection

The provider is included in the default sources at Information level. Enable
forwarding with `io.microsoft.virtualmachine.forwardlogs.enable = true`.
C-WCOW forwarding remains disabled by default. Every requested default provider
must satisfy provider policy: a disallowed provider is dropped if dropping is
enabled, or the request fails otherwise. Under the existing confidential startup
behavior, that failure aborts UVM startup, even if runtime logging is disabled.

To collect only sidecar logs at Verbose level, set
`io.microsoft.virtualmachine.forwardlogs.defaultsources.enable = false` and
set `io.microsoft.virtualmachine.forwardlogs.sources` to the base64 encoding of:

```json
{
"LogConfig": {
"sources": [{
"type": "ETW",
"providers": [{
"providerName": "Microsoft.Virtualization.GCSSidecar",
"level": "Verbose"
}]
}]
}
}
```

Provider names are matched case-insensitively when overriding default levels.
The sidecar Logrus level also filters events; debug and trace map to ETW
Verbose. Existing guest-local file output is retained when runtime logging is
allowed. Startup buffering is capped at 128 events and 256 KiB; individual
events larger than 16 KiB are dropped. Once streaming starts, events produced
while collection is disabled are not buffered for later replay.

Delivery is best effort. ETW events emitted before collection starts may be
lost; direct stderr writes and Go panic dumps are not forwarded by the hook.
Provider initialization failures are reported locally without terminating the
sidecar. Stopping Log Forward Service stops forwarding, not authorized local
ETW emission.

On non-confidential WCOW guests, an absent sidecar provider does not require
installing the sidecar. Guests without log-forwarding capability retain the
existing unsupported-feature behavior; supported collectors can subscribe to
the provider GUID even when no process has registered it.
4 changes: 2 additions & 2 deletions internal/gcs-sidecar/bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ type request struct {
message []byte
}

func NewBridge(shimConn io.ReadWriteCloser, inboxGCSConn io.ReadWriteCloser, initialEnforcer securitypolicy.SecurityPolicyEnforcer, logWriter io.Writer) *Bridge {
hostState := NewHost(initialEnforcer, logWriter)
func NewBridge(shimConn io.ReadWriteCloser, inboxGCSConn io.ReadWriteCloser, initialEnforcer securitypolicy.SecurityPolicyEnforcer, logWriter io.Writer, options ...func(*securitypolicy.SecurityOptions)) *Bridge {
hostState := NewHost(initialEnforcer, logWriter, options...)
return &Bridge{
pending: make(map[sequenceID]chan *prot.ContainerExecuteProcessResponse),
monitoredIDs: make(map[sequenceID]struct{}),
Expand Down
3 changes: 2 additions & 1 deletion internal/gcs-sidecar/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,14 @@ type containerProcess struct {
pid uint32
}

func NewHost(initialEnforcer securitypolicy.SecurityPolicyEnforcer, logWriter io.Writer) *Host {
func NewHost(initialEnforcer securitypolicy.SecurityPolicyEnforcer, logWriter io.Writer, options ...func(*securitypolicy.SecurityOptions)) *Host {
securityPolicyOptions := securitypolicy.NewSecurityOptions(
initialEnforcer,
false,
"",
"",
logWriter,
options...,
)
return &Host{
containers: make(map[string]*Container),
Expand Down
Loading