| 1 | //go:build windows |
| 2 | |
| 3 | package packagegrant |
| 4 | |
| 5 | import ( |
| 6 | "encoding/binary" |
| 7 | "io/fs" |
| 8 | "os" |
| 9 | "path/filepath" |
| 10 | "runtime" |
| 11 | "unsafe" |
| 12 | |
| 13 | "golang.org/x/sys/windows" |
| 14 | ) |
| 15 | |
| 16 | // Strip removes package grants from root and everything under it, parents |
| 17 | // before children, so a directory's inheritable entries are gone before its |
| 18 | // children are read. Links and reparse points are not followed: what they |
| 19 | // point at is not this tree. |
| 20 | func Strip(root string) (Report, error) { |
| 21 | report := Report{Stripped: []string{}, Refused: []Failure{}, Unread: []Failure{}} |
| 22 | info, err := os.Stat(root) |
| 23 | if err != nil { |
| 24 | return report, err |
| 25 | } |
| 26 | if !info.IsDir() { |
| 27 | return report, fs.ErrInvalid |
| 28 | } |
| 29 | walkErr := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { |
| 30 | if err != nil { |
| 31 | report.Unread = append(report.Unread, Failure{Path: path, Err: err}) |
| 32 | return nil |
| 33 | } |
| 34 | if !d.IsDir() && !d.Type().IsRegular() { |
| 35 | return nil |
| 36 | } |
| 37 | acl, protect, changed, err := withoutGrants(path) |
| 38 | if err != nil { |
| 39 | report.Unread = append(report.Unread, Failure{Path: path, Err: err}) |
| 40 | return nil |
| 41 | } |
| 42 | if !changed { |
| 43 | return nil |
| 44 | } |
| 45 | if err := writeDACL(path, acl, protect); err != nil { |
| 46 | report.Refused = append(report.Refused, Failure{Path: path, Err: err}) |
| 47 | return nil |
| 48 | } |
| 49 | report.Stripped = append(report.Stripped, path) |
| 50 | return nil |
| 51 | }) |
| 52 | return report, walkErr |
| 53 | } |
| 54 | |
| 55 | // withoutGrants reads path's DACL and returns it with the package grants gone. |
| 56 | func withoutGrants(path string) (acl []byte, protect, changed bool, err error) { |
| 57 | sd, err := windows.GetNamedSecurityInfo(path, windows.SE_FILE_OBJECT, windows.DACL_SECURITY_INFORMATION) |
| 58 | if err != nil { |
| 59 | return nil, false, false, err |
| 60 | } |
| 61 | dacl, _, err := sd.DACL() |
| 62 | if err != nil || dacl == nil { |
| 63 | // No DACL names nobody, so there is nothing a package grant could be. |
| 64 | return nil, false, false, nil |
| 65 | } |
| 66 | control, _, err := sd.Control() |
| 67 | if err != nil { |
| 68 | return nil, false, false, err |
| 69 | } |
| 70 | header := unsafe.Slice((*byte)(unsafe.Pointer(dacl)), aclHeaderSize) |
| 71 | raw := unsafe.Slice((*byte)(unsafe.Pointer(dacl)), binary.LittleEndian.Uint16(header[2:4])) |
| 72 | acl, protect, changed, err = rewrite(raw, control&windows.SE_DACL_PROTECTED != 0) |
| 73 | runtime.KeepAlive(sd) |
| 74 | return acl, protect, changed, err |
| 75 | } |
| 76 | |
| 77 | func writeDACL(path string, acl []byte, protect bool) error { |
| 78 | var inheritance windows.SECURITY_INFORMATION = windows.UNPROTECTED_DACL_SECURITY_INFORMATION |
| 79 | if protect { |
| 80 | inheritance = windows.PROTECTED_DACL_SECURITY_INFORMATION |
| 81 | } |
| 82 | err := windows.SetNamedSecurityInfo(path, windows.SE_FILE_OBJECT, windows.DACL_SECURITY_INFORMATION|inheritance, |
| 83 | nil, nil, (*windows.ACL)(unsafe.Pointer(&acl[0])), nil) |
| 84 | runtime.KeepAlive(acl) |
| 85 | return err |
| 86 | } |
| 87 |