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  }