google_guest_agent/command/command_windows.go (68 lines of code) (raw):

// Copyright 2023 Google Inc. All Rights Reserved. // // 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 command import ( "context" "fmt" "net" "os/user" "github.com/GoogleCloudPlatform/guest-logging-go/logger" "github.com/Microsoft/go-winio" ) const ( // DefaultPipePath is the default named pipe path for windows. DefaultPipePath = `\\.\pipe\google-guest-agent-commands` nullSID = "S-1-0-0" worldSID = "S-1-1-0" creatorOwnerSID = "S-1-3-0" creatorGroupSID = "S-1-3-1" ) func genSecurityDescriptor(filemode int, grp string) string { // This function translates the intention of a unix file mode and owner group into an appropriate SDDL security descriptor for a windows named pipe. owner := creatorOwnerSID group := creatorGroupSID wPerm := filemode % 010 filemode /= 010 gPerm := filemode % 010 filemode /= 010 uPerm := filemode % 010 // Having only read or only write access to a bidirectional pipe is pointless so we treat access for user/group as yes or no based on whether the permission grants RW access if uPerm < 06 { owner = nullSID } if gPerm < 06 { group = nullSID } // If permissions grant world RW, make world the owner if wPerm > 05 { owner = worldSID group = worldSID } // Group is handled as supplemental DACL, but ignore it if user specified no group rw permission var dacl string if gPerm > 05 { g, err := user.LookupGroup(grp) if err != nil { logger.Errorf("Could not lookup group %s SID, this group will not be included in the command server security descriptor: %v", grp, err) } else { // Allow access;Protected DACL;Allow all general access;Empty object guid;Empty inherit object guid;group sid from lookup dacl = fmt.Sprintf("D:(A;P;GA;;;%s)", g.Gid) } } sddl := "O:%sG:%s%s" return fmt.Sprintf(sddl, owner, group, dacl) } func listen(ctx context.Context, path string, filemode int, group string) (net.Listener, error) { // Winio library does not provide any method to listen on context. Failing to // specify a pipeconfig (or using the zero value) results in flaky ACCESS_DENIED // errors when re-opening the same pipe (~1/10). // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea#remarks // Even with a pipeconfig, this flakes ~1/200 runs, hence the retry until the // context is expired or listen is successful. var l net.Listener var lastError error for { if ctx.Err() != nil { return nil, fmt.Errorf("context expired: %v before successful listen (last error: %v)", ctx.Err(), lastError) } config := &winio.PipeConfig{ MessageMode: false, InputBufferSize: 1024, OutputBufferSize: 1024, SecurityDescriptor: genSecurityDescriptor(filemode, group), } l, lastError = winio.ListenPipe(path, config) if lastError == nil { return l, lastError } } } func dialPipe(ctx context.Context, pipe string) (net.Conn, error) { return winio.DialPipeContext(ctx, pipe) }