返回 DeepSeek-Reasonix
credential_diagnostics_windows.go
根目录 / internal / config / credential_diagnostics_windows.go
1 //go:build windows
2
3 package config
4
5 import (
6 "errors"
7 "fmt"
8 "os"
9 "unsafe"
10
11 "golang.org/x/sys/windows"
12 )
13
14 func credentialPlatformInspect(path string, _ os.FileInfo) (string, bool, bool, bool, error) {
15 ptr, err := windows.UTF16PtrFromString(path)
16 if err != nil {
17 return "", false, false, false, err
18 }
19 attrs, err := windows.GetFileAttributes(ptr)
20 if err != nil {
21 return "", false, false, false, err
22 }
23 sd, err := windows.GetNamedSecurityInfo(path, windows.SE_FILE_OBJECT, windows.OWNER_SECURITY_INFORMATION)
24 if err != nil || sd == nil {
25 return "owner unavailable", false, attrs&windows.FILE_ATTRIBUTE_READONLY != 0, attrs&windows.FILE_ATTRIBUTE_REPARSE_POINT != 0, err
26 }
27 owner, _, err := sd.Owner()
28 if err != nil {
29 return "owner unavailable", false, attrs&windows.FILE_ATTRIBUTE_READONLY != 0, attrs&windows.FILE_ATTRIBUTE_REPARSE_POINT != 0, err
30 }
31 user, err := windows.GetCurrentProcessToken().GetTokenUser()
32 if err != nil {
33 return owner.String(), false, attrs&windows.FILE_ATTRIBUTE_READONLY != 0, attrs&windows.FILE_ATTRIBUTE_REPARSE_POINT != 0, err
34 }
35 return fmt.Sprintf("owner %s (current user %s)", owner.String(), user.User.Sid.String()), owner.Equals(user.User.Sid), attrs&windows.FILE_ATTRIBUTE_READONLY != 0, attrs&windows.FILE_ATTRIBUTE_REPARSE_POINT != 0, nil
36 }
37
38 func credentialPlatformRepair(path string, expected os.FileInfo, verify func() error) ([]string, error) {
39 ptr, err := windows.UTF16PtrFromString(path)
40 if err != nil {
41 return nil, err
42 }
43 // No delete sharing: the inspected file cannot be replaced during repair.
44 handle, err := windows.CreateFile(ptr, windows.READ_CONTROL|windows.WRITE_DAC|windows.FILE_WRITE_ATTRIBUTES|windows.FILE_READ_ATTRIBUTES,
45 windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE, nil, windows.OPEN_EXISTING, windows.FILE_FLAG_OPEN_REPARSE_POINT, 0)
46 if err != nil {
47 return nil, err
48 }
49 f := os.NewFile(uintptr(handle), path)
50 defer f.Close()
51 info, err := f.Stat()
52 if err != nil {
53 return nil, err
54 }
55 if !os.SameFile(expected, info) {
56 return nil, fmt.Errorf("credential file identity changed")
57 }
58 var basic struct {
59 CreationTime, LastAccessTime, LastWriteTime, ChangeTime int64
60 FileAttributes uint32
61 Padding uint32
62 }
63 if err := windows.GetFileInformationByHandleEx(handle, windows.FileBasicInfo, (*byte)(unsafe.Pointer(&basic)), uint32(unsafe.Sizeof(basic))); err != nil {
64 return nil, err
65 }
66 if basic.FileAttributes&windows.FILE_ATTRIBUTE_REPARSE_POINT != 0 {
67 return nil, fmt.Errorf("repair refuses reparse points")
68 }
69 sd, err := windows.GetSecurityInfo(handle, windows.SE_FILE_OBJECT, windows.DACL_SECURITY_INFORMATION|windows.OWNER_SECURITY_INFORMATION)
70 if err != nil {
71 return nil, err
72 }
73 if sd == nil {
74 return nil, fmt.Errorf("security descriptor unavailable")
75 }
76 dacl, _, err := sd.DACL()
77 if err != nil {
78 return nil, err
79 }
80 user, err := windows.GetCurrentProcessToken().GetTokenUser()
81 if err != nil {
82 return nil, err
83 }
84 owner, _, err := sd.Owner()
85 if err != nil || owner == nil || !owner.Equals(user.User.Sid) {
86 return nil, fmt.Errorf("credential owner could not be verified")
87 }
88 entry := windows.EXPLICIT_ACCESS{
89 AccessPermissions: windows.FILE_GENERIC_READ | windows.FILE_GENERIC_WRITE | windows.DELETE,
90 AccessMode: windows.GRANT_ACCESS,
91 Inheritance: windows.NO_INHERITANCE,
92 Trustee: windows.TRUSTEE{TrusteeForm: windows.TRUSTEE_IS_SID, TrusteeType: windows.TRUSTEE_IS_USER, TrusteeValue: windows.TrusteeValueFromSID(user.User.Sid)},
93 }
94 // A null DACL already permits access. Replacing it with a one-user ACL
95 // would revoke other principals' existing access, outside repair's scope.
96 var merged *windows.ACL
97 if dacl != nil {
98 merged, err = windows.ACLFromEntries([]windows.EXPLICIT_ACCESS{entry}, dacl)
99 if err != nil {
100 return nil, err
101 }
102 }
103 setAttributes := func(attrs uint32) error {
104 // Zero timestamps mean unchanged; do not roll back concurrent file data.
105 attributes := basic
106 attributes.CreationTime, attributes.LastAccessTime, attributes.LastWriteTime, attributes.ChangeTime = 0, 0, 0, 0
107 attributes.FileAttributes = attrs
108 if attrs == 0 {
109 attributes.FileAttributes = windows.FILE_ATTRIBUTE_NORMAL
110 }
111 return windows.SetFileInformationByHandle(handle, windows.FileBasicInfo, (*byte)(unsafe.Pointer(&attributes)), uint32(unsafe.Sizeof(attributes)))
112 }
113 if err := setAttributes(basic.FileAttributes &^ windows.FILE_ATTRIBUTE_READONLY); err != nil {
114 return nil, err
115 }
116 rollback := func(cause error) ([]string, error) {
117 err := errors.Join(windows.SetSecurityInfo(handle, windows.SE_FILE_OBJECT, windows.DACL_SECURITY_INFORMATION, nil, nil, dacl, nil), setAttributes(basic.FileAttributes))
118 if err != nil {
119 return nil, errors.Join(fmt.Errorf("repair failed: %w", cause), fmt.Errorf("rollback failed: %w", err))
120 }
121 return nil, fmt.Errorf("repair failed: %w; changed attributes were restored", cause)
122 }
123 if merged != nil {
124 if err := windows.SetSecurityInfo(handle, windows.SE_FILE_OBJECT, windows.DACL_SECURITY_INFORMATION, nil, nil, merged, nil); err != nil {
125 return rollback(err)
126 }
127 }
128 if err := verify(); err != nil {
129 return rollback(err)
130 }
131 return []string{"cleared read-only state and added current-user access without removing existing ACL entries"}, nil
132 }
133
133 lines GO