github.com/google/osv-scalibr@v0.4.1/extractor/standalone/windows/regosversion/regosversion_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 regosversion extracts the OS version (build, major, minor release) from the registry. 18 package regosversion 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/extractor/standalone/windows/common/metadata" 29 "github.com/google/osv-scalibr/extractor/standalone/windows/common/winproducts" 30 "github.com/google/osv-scalibr/inventory" 31 "github.com/google/osv-scalibr/plugin" 32 ) 33 34 const ( 35 // Name of the DISM patch level extractor 36 Name = "windows/regosversion" 37 regVersionPath = `SOFTWARE\Microsoft\Windows NT\CurrentVersion` 38 ) 39 40 // Configuration for the extractor. 41 type Configuration struct { 42 // Opener is the registry engine to use (offline, live or mock). 43 Opener registry.Opener 44 } 45 46 // DefaultConfiguration for the extractor. It uses the live registry of the running system. 47 func DefaultConfiguration() Configuration { 48 return Configuration{ 49 Opener: registry.NewLiveOpener(), 50 } 51 } 52 53 // Extractor provides a metadata extractor for the patch level on Windows. 54 type Extractor struct { 55 opener registry.Opener 56 } 57 58 // New creates a new Extractor from a given configuration. 59 func New(config Configuration) standalone.Extractor { 60 return &Extractor{ 61 opener: config.Opener, 62 } 63 } 64 65 // NewDefault returns an extractor with the default config settings. 66 func NewDefault() standalone.Extractor { 67 return New(DefaultConfiguration()) 68 } 69 70 // Name of the extractor. 71 func (e Extractor) Name() string { return Name } 72 73 // Version of the extractor. 74 func (e Extractor) Version() int { return 0 } 75 76 // Requirements of the extractor. 77 func (e Extractor) Requirements() *plugin.Capabilities { 78 return &plugin.Capabilities{OS: plugin.OSWindows, RunningSystem: true} 79 } 80 81 // Extract the DISM patch level on Windows. 82 func (e *Extractor) Extract(ctx context.Context, input *standalone.ScanInput) (inventory.Inventory, error) { 83 reg, err := e.opener.Open() 84 if err != nil { 85 return inventory.Inventory{}, err 86 } 87 defer reg.Close() 88 89 key, err := reg.OpenKey("HKLM", regVersionPath) 90 if err != nil { 91 return inventory.Inventory{}, err 92 } 93 defer key.Close() 94 95 currentVersion, err := e.windowsVersion(key) 96 if err != nil { 97 return inventory.Inventory{}, err 98 } 99 100 // CurrentBuildNumber should be available on a large range of Windows versions. 101 buildNumber, err := key.ValueString("CurrentBuildNumber") 102 if err != nil { 103 return inventory.Inventory{}, err 104 } 105 106 revision, err := e.windowsRevision(key) 107 if err != nil { 108 return inventory.Inventory{}, err 109 } 110 111 flavor := winproducts.WindowsFlavorFromRegistry(reg) 112 fullVersion := fmt.Sprintf("%s.%s.%s", currentVersion, buildNumber, revision) 113 winproduct := winproducts.WindowsProductFromVersion(flavor, fullVersion) 114 return inventory.Inventory{Packages: []*extractor.Package{ 115 { 116 Name: winproduct, 117 Version: fullVersion, 118 PURLType: "windows", 119 Metadata: &metadata.OSVersion{ 120 Product: winproduct, 121 FullVersion: fullVersion, 122 }, 123 }, 124 }}, nil 125 } 126 127 // windowsVersion extracts the version of Windows (major and minor, e.g. 6.3 or 10.0) 128 func (e Extractor) windowsVersion(key registry.Key) (string, error) { 129 // recent version of Windows 130 majorVersion, majorErr := key.ValueString("CurrentMajorVersionNumber") 131 minorVersion, minorErr := key.ValueString("CurrentMinorVersionNumber") 132 133 if majorErr == nil && minorErr == nil { 134 return fmt.Sprintf("%s.%s", majorVersion, minorVersion), nil 135 } 136 137 // older versions of Windows 138 return key.ValueString("CurrentVersion") 139 } 140 141 // windowsRevision extracts the revision within the current build. 142 func (e Extractor) windowsRevision(key registry.Key) (string, error) { 143 // recent version of Windows 144 if revision, err := key.ValueString("UBR"); err == nil { 145 return revision, nil 146 } 147 148 // on older version, we have to parse the BuildLabEx key 149 buildLabEx, err := key.ValueString("BuildLabEx") 150 if err != nil { 151 return "", err 152 } 153 154 buildLabParts := strings.Split(buildLabEx, ".") 155 if len(buildLabParts) < 2 { 156 return "", fmt.Errorf("could not parse BuildLabEx: %q", buildLabEx) 157 } 158 159 return buildLabParts[1], nil 160 }