github.com/google/fleetspeak@v0.1.15-0.20240426164851-4f31f62c1aea/fleetspeak/src/windows/wnixsocket/wnixsocket_windows.go (about)

     1  // Copyright 2017 Google Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     https://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  //go:build windows
    16  
    17  // Package wnixsocket provides Unix-like sockets on Windows.
    18  package wnixsocket
    19  
    20  import (
    21  	"fmt"
    22  	"net"
    23  	"os"
    24  	"time"
    25  
    26  	"github.com/Microsoft/go-winio"
    27  	"github.com/google/fleetspeak/fleetspeak/src/windows/hashpipe"
    28  	"github.com/hectane/go-acl"
    29  
    30  	"golang.org/x/sys/windows"
    31  )
    32  
    33  // Listen prepares a net.Listener bound to the given filesystem path.
    34  func Listen(socketPath string) (net.Listener, error) {
    35  	// Allow Administrators and SYSTEM. See:
    36  	// - SDDL format:
    37  	//   https://msdn.microsoft.com/en-us/library/windows/desktop/aa379570(v=vs.85).aspx
    38  	// - ACE (rules in brackets) format:
    39  	//   https://msdn.microsoft.com/en-us/library/windows/desktop/aa374928(v=vs.85).aspx
    40  	// - SID string (user handles, `BA' and `SY') format:
    41  	//   https://msdn.microsoft.com/en-us/library/windows/desktop/aa379602(v=vs.85).aspx
    42  	// - Protecting Objects from the Effects of Inherited Rights:
    43  	//   https://msdn.microsoft.com/en-us/library/ms677634(v=vs.85).aspx
    44  	//
    45  	// The above links are quite elaborate. Including the relevant items here for
    46  	// easier lookup; note that the order is important:
    47  	// - D stands for DACL
    48  	// - P disables access perms inheritance
    49  	// - In brackets ():
    50  	// -- A means allow access to the specified user
    51  	// -- GA means read, write and execute
    52  	// -- BA means apply to `built-in administrators'
    53  	// -- SY means apply to `local system'
    54  	const sddl = "D:P(A;;GA;;;BA)(A;;GA;;;SY)"
    55  	c := &winio.PipeConfig{
    56  		SecurityDescriptor: sddl,
    57  	}
    58  
    59  	l, pipeFSPath, err := hashpipe.ListenPipe(c)
    60  	if err != nil {
    61  		return nil, fmt.Errorf("failed to listen on a hashpipe: %v", err)
    62  	}
    63  
    64  	// Previous versions of Fleetspeak had a bug where on go1.14 the
    65  	// WriteFile below would create a read-only file. Clear that attribute
    66  	// if such a file has been left behind.
    67  	if _, err := os.Stat(socketPath); !os.IsNotExist(err) {
    68  		if err := windows.Chmod(socketPath, windows.S_IWRITE); err != nil {
    69  			return nil, fmt.Errorf("clearing read-only bit on wnix socket: %w", err)
    70  		}
    71  	}
    72  
    73  	// The socket file will be truncated if it exists. Otherwise it wil be
    74  	// created with the attributes described by the permission bits.
    75  	if err := os.WriteFile(socketPath, []byte{}, 0600); err != nil {
    76  		return nil, fmt.Errorf("error while truncating a Wnix socket file; os.WriteFile(%q, ...): %v", socketPath, err)
    77  	}
    78  
    79  	// WriteFile doesn't set ACLs as expected on Windows, so we make
    80  	// sure with Chmod. Note that os.Chmod also doesn't work as expected, so we
    81  	// use go-acl.
    82  	if err := acl.Chmod(socketPath, 0600); err != nil {
    83  		return nil, fmt.Errorf("failed to chmod a Wnix pipe: %v", err)
    84  	}
    85  
    86  	// Note that we only write the pipeFSPath to a file after we've reserved the
    87  	// pipe name and chmoded it. The order is important for hardening purposes.
    88  	// Note that the third param _is_ significant here even though the file
    89  	// already exists - if it was 0 this call would set the read-only
    90  	// attribute.
    91  	if err := os.WriteFile(socketPath, []byte(pipeFSPath), 0600); err != nil {
    92  		return nil, fmt.Errorf("failed to initialize a Wnix socket: %v", err)
    93  	}
    94  
    95  	return l, nil
    96  }
    97  
    98  // Dial dials a Wnix socket bound to the given filesystem path.
    99  func Dial(socketPath string, timeout time.Duration) (net.Conn, error) {
   100  	bytePipeFSPath, err := os.ReadFile(socketPath)
   101  	if err != nil {
   102  		return nil, fmt.Errorf("failed to dial to a Wnix socket (socketPath: [%v]): %v", socketPath, err)
   103  	}
   104  
   105  	pipeFSPath := string(bytePipeFSPath)
   106  
   107  	if pipeFSPath == "" {
   108  		return nil, fmt.Errorf("the dialed socket is not initialized (socketPath: [%v]): %v", socketPath, err)
   109  	}
   110  
   111  	conn, err := winio.DialPipe(pipeFSPath, &timeout)
   112  	if err != nil {
   113  		return nil, fmt.Errorf("winio.DialPipe() failed (pipeFSPath: [%q], socketPath: [%v]): %v", pipeFSPath, socketPath, err)
   114  	}
   115  
   116  	return conn, nil
   117  }