github.com/google/osv-scalibr@v0.4.1/extractor/standalone/windows/ospackages/ospackages_windows.go (about) 1 // Copyright 2025 Google LLC 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 // http://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 ospackages extracts installed softwares on Windows. 18 package ospackages 19 20 import ( 21 "context" 22 "fmt" 23 "strings" 24 25 "github.com/google/osv-scalibr/common/windows/registry" 26 "github.com/google/osv-scalibr/extractor" 27 "github.com/google/osv-scalibr/extractor/standalone" 28 "github.com/google/osv-scalibr/inventory" 29 "github.com/google/osv-scalibr/plugin" 30 "github.com/google/osv-scalibr/purl" 31 ) 32 33 const ( 34 // regUninstallRootWow64 is the registry key for 32-bit software on 64-bit Windows. 35 regUninstallRootWow64 = `SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall` 36 regUninstallRootDefault = `SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall` 37 regUninstallRelativeUsers = `Software\Microsoft\Windows\CurrentVersion\Uninstall` 38 39 // googetPrefix identifies GooGet packages. 40 googetPrefix = "GooGet -" 41 ) 42 43 // Configuration for the extractor. 44 type Configuration struct { 45 // Opener is the registry engine to use (offline, live or mock). 46 Opener registry.Opener 47 } 48 49 // DefaultConfiguration for the extractor. It uses the live registry of the running system. 50 func DefaultConfiguration() Configuration { 51 return Configuration{ 52 Opener: registry.NewLiveOpener(), 53 } 54 } 55 56 // Name of the extractor 57 const Name = "windows/ospackages" 58 59 // Extractor implements the ospackages extractor. 60 type Extractor struct { 61 opener registry.Opener 62 } 63 64 // New creates a new Extractor from a given configuration. 65 func New(config Configuration) standalone.Extractor { 66 return &Extractor{ 67 opener: config.Opener, 68 } 69 } 70 71 // NewDefault returns an extractor with the default config settings. 72 func NewDefault() standalone.Extractor { 73 return New(DefaultConfiguration()) 74 } 75 76 // Name of the extractor. 77 func (e Extractor) Name() string { return Name } 78 79 // Version of the extractor. 80 func (e Extractor) Version() int { return 0 } 81 82 // Requirements of the extractor. 83 func (e Extractor) Requirements() *plugin.Capabilities { 84 return &plugin.Capabilities{OS: plugin.OSWindows, RunningSystem: true} 85 } 86 87 // Extract retrieves the patch level from the Windows registry. 88 func (e *Extractor) Extract(ctx context.Context, input *standalone.ScanInput) (inventory.Inventory, error) { 89 reg, err := e.opener.Open() 90 if err != nil { 91 return inventory.Inventory{}, err 92 } 93 94 defer reg.Close() 95 96 // First extract the system-level installed software, both for x64 and x86. 97 sysKeys, err := e.installedSystemSoftware(reg) 98 if err != nil { 99 return inventory.Inventory{}, err 100 } 101 102 pkg := e.allSoftwaresInfo(reg, "HKLM", sysKeys) 103 104 // Then we extract user-level installed software. 105 userKeys, err := e.installedUserSoftware(reg) 106 if err != nil { 107 return inventory.Inventory{}, err 108 } 109 110 pkgs := e.allSoftwaresInfo(reg, "HKU", userKeys) 111 return inventory.Inventory{Packages: append(pkgs, pkg...)}, nil 112 } 113 114 // allSoftwaresInfo builds the package of name/version for installed software from the given registry 115 // keys. This function cannot return an error. 116 func (e *Extractor) allSoftwaresInfo(reg registry.Registry, hive string, paths []string) []*extractor.Package { 117 var pkgs []*extractor.Package 118 119 for _, p := range paths { 120 // Silently swallow errors as some software might not have a name or version. 121 // For example, paint will be a subkey of the registry key, but it does not have a version. 122 if pkg, err := e.softwareInfo(reg, hive, p); err == nil { 123 pkgs = append(pkgs, pkg) 124 } 125 } 126 127 return pkgs 128 } 129 130 func (e *Extractor) softwareInfo(reg registry.Registry, hive string, path string) (*extractor.Package, error) { 131 key, err := reg.OpenKey(hive, path) 132 if err != nil { 133 return nil, err 134 } 135 defer key.Close() 136 137 displayName, err := key.ValueString("DisplayName") 138 if err != nil { 139 return nil, err 140 } 141 142 displayVersion, err := key.ValueString("DisplayVersion") 143 if err != nil { 144 return nil, err 145 } 146 147 purlType := "windows" 148 if strings.HasPrefix(displayName, googetPrefix) { 149 purlType = purl.TypeGooget 150 } 151 return &extractor.Package{ 152 Name: displayName, 153 Version: displayVersion, 154 PURLType: purlType, 155 }, nil 156 } 157 158 func (e *Extractor) installedSystemSoftware(reg registry.Registry) ([]string, error) { 159 keys, err := e.enumerateSubkeys(reg, "HKLM", regUninstallRootDefault) 160 if err != nil { 161 return nil, err 162 } 163 164 k, err := e.enumerateSubkeys(reg, "HKLM", regUninstallRootWow64) 165 if err != nil { 166 return nil, err 167 } 168 169 return append(keys, k...), nil 170 } 171 172 func (e *Extractor) installedUserSoftware(reg registry.Registry) ([]string, error) { 173 var keys []string 174 175 userHives, err := e.enumerateSubkeys(reg, "HKU", "") 176 if err != nil { 177 return nil, err 178 } 179 180 for _, userHive := range userHives { 181 regPath := fmt.Sprintf(`%s\%s`, userHive, regUninstallRelativeUsers) 182 regPath = strings.TrimPrefix(regPath, `\`) 183 184 // Note that the key might not exist or be accessible for all users, so we silently ignore 185 // errors here. 186 if k, err := e.enumerateSubkeys(reg, "HKU", regPath); err == nil { 187 keys = append(keys, k...) 188 } 189 } 190 191 return keys, nil 192 } 193 194 func (e *Extractor) enumerateSubkeys(reg registry.Registry, hive string, path string) ([]string, error) { 195 key, err := reg.OpenKey(hive, path) 196 if err != nil { 197 return nil, err 198 } 199 defer key.Close() 200 201 subkeys, err := key.SubkeyNames() 202 if err != nil { 203 return nil, err 204 } 205 206 var paths []string 207 for _, subkey := range subkeys { 208 paths = append(paths, fmt.Sprintf(`%s\%s`, path, subkey)) 209 } 210 211 return paths, nil 212 }