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 }