Skip to content

Commit

Permalink
Extractor for Windows patch level. Uses the `dism /get-packages /onli…
Browse files Browse the repository at this point in the history
…ne` command under the hood.

PiperOrigin-RevId: 630352135
  • Loading branch information
tooryx authored and copybara-github committed May 14, 2024
1 parent 6a999b0 commit 0f9acb4
Show file tree
Hide file tree
Showing 9 changed files with 747 additions and 30 deletions.
107 changes: 78 additions & 29 deletions converter/converter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -319,41 +319,90 @@ func TestToSPDX23(t *testing.T) {
}

func TestToPURL(t *testing.T) {
inventory := &extractor.Inventory{
Name: "software",
Version: "1.0.0",
Locations: []string{"/file1"},
Extractor: "python/wheelegg",
}
want := &purl.PackageURL{
Type: purl.TypePyPi,
Name: "software",
Version: "1.0.0",
}
got, err := converter.ToPURL(inventory)
if err != nil {
t.Fatalf("converter.ToPURL(%v): %v", inventory, err)
tests := []struct {
desc string
inventory *extractor.Inventory
want *purl.PackageURL
wantErr bool
}{
{
desc: "Valid inventory extractor",
inventory: &extractor.Inventory{
Name: "software",
Version: "1.0.0",
Locations: []string{"/file1"},
Extractor: "python/wheelegg",
},
want: &purl.PackageURL{
Type: purl.TypePyPi,
Name: "software",
Version: "1.0.0",
},
},
{
desc: "Windows-only returns error",
inventory: &extractor.Inventory{
Name: "irrelevant",
Extractor: "windows/dismpatch",
Locations: []string{"irrelevant"},
Version: "irrelevant",
},
wantErr: true,
},
}
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("converter.ToPURL(%v) returned unexpected diff (-want +got):\n%s", inventory, diff)

for _, tc := range tests {
t.Run(tc.desc, func(t *testing.T) {
got, err := converter.ToPURL(tc.inventory)
if err != nil && !tc.wantErr || err == nil && tc.wantErr {
t.Fatalf("converter.ToPURL(%v): %v", tc.inventory, err)
}

if tc.wantErr == true {
return
}

if diff := cmp.Diff(tc.want, got); diff != "" {
t.Errorf("converter.ToPURL(%v) returned unexpected diff (-want +got):\n%s", tc.inventory, diff)
}
})
}
}

func TestToCPEs(t *testing.T) {
cpes := []string{"cpe:2.3:a:nginx:nginx:1.21.1"}
inventory := &extractor.Inventory{
Name: "nginx",
Metadata: &spdx.Metadata{
CPEs: cpes,
tests := []struct {
desc string
inventory *extractor.Inventory
want []string
wantErr bool
}{
{
desc: "Valid fileststem extractor",
inventory: &extractor.Inventory{
Name: "nginx",
Metadata: &spdx.Metadata{
CPEs: []string{"cpe:2.3:a:nginx:nginx:1.21.1"},
},
Extractor: "sbom/spdx",
},
want: []string{"cpe:2.3:a:nginx:nginx:1.21.1"},
},
Extractor: "sbom/spdx",
}
want := cpes
got, err := converter.ToCPEs(inventory)
if err != nil {
t.Fatalf("converter.ToCPEs(%v): %v", inventory, err)
}
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("converter.ToCPEs(%v) returned unexpected diff (-want +got):\n%s", inventory, diff)

for _, tc := range tests {
t.Run(tc.desc, func(t *testing.T) {
got, err := converter.ToCPEs(tc.inventory)
if err != nil && !tc.wantErr || err == nil && tc.wantErr {
t.Fatalf("converter.ToCPEs(%v): %v", tc.inventory, err)
}

if tc.wantErr == true {
return
}

if diff := cmp.Diff(tc.want, got); diff != "" {
t.Errorf("converter.ToCPEs(%v) returned unexpected diff (-want +got):\n%s", tc.inventory, diff)
}
})
}
}
5 changes: 4 additions & 1 deletion extractor/standalone/list/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,15 @@ import (
"strings"

"github.com/google/osv-scalibr/extractor/standalone"
"github.com/google/osv-scalibr/extractor/standalone/windows/dismpatch"
"github.com/google/osv-scalibr/log"
)

var (
// Windows standalone extractors.
Windows = []standalone.Extractor{}
Windows = []standalone.Extractor{
&dismpatch.Extractor{},
}

// Default standalone extractors.
Default []standalone.Extractor = slices.Concat(Windows)
Expand Down
83 changes: 83 additions & 0 deletions extractor/standalone/windows/dismpatch/dismparser/dism_parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package dismparser has methods that can be used to parse DISM output
package dismparser

import (
"errors"
"regexp"
"strings"
)

// DismPkg reports information about a package as reported by the DISM tool.
type DismPkg struct {
PackageIdentity string
PackageVersion string
State string
ReleaseType string
InstallTime string
}

// Parse parses dism output into an array of dismPkgs.
func Parse(input string) ([]DismPkg, string, error) {
pkgs := strings.Split(input, "Package Id")

pkgExp, err := regexp.Compile("entity :(.*)\n*State :(.*)\n*Release Type :(.*)\n*Install Time :(.*)\n*")
if err != nil {
return nil, "", err
}

imgExp, err := regexp.Compile("Image Version: (.*)")
if err != nil {
return nil, "", err
}

imgVersion := ""
dismPkgs := []DismPkg{}

for _, pkg := range pkgs {
matches := pkgExp.FindStringSubmatch(pkg)
if len(matches) > 4 {
dismPkg := DismPkg{
PackageIdentity: strings.TrimSpace(matches[1]),
State: strings.TrimSpace(matches[2]),
ReleaseType: strings.TrimSpace(matches[3]),
InstallTime: strings.TrimSpace(matches[4]),
}
dismPkg.PackageVersion = findVersion(dismPkg.PackageIdentity)
dismPkgs = append(dismPkgs, dismPkg)
} else {
// this is the first entry that has the image version
matches = imgExp.FindStringSubmatch(pkg)
if len(matches) > 1 {
imgVersion = matches[1]
}
}
}

if len(dismPkgs) == 0 {
return nil, "", errors.New("Could not parse DISM output successfully")
}

return dismPkgs, imgVersion, nil
}

func findVersion(identity string) string {
pkgVer := strings.Split(identity, "~~")
if len(pkgVer) > 1 {
return pkgVer[1]
}
return ""
}
115 changes: 115 additions & 0 deletions extractor/standalone/windows/dismpatch/dismparser/dism_parser_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package dismparser

import (
"os"
"testing"

"github.com/google/go-cmp/cmp"
)

func TestParse(t *testing.T) {
content, err := os.ReadFile("testdata/dism_testdata.txt")
if err != nil {
t.Fatalf("Failed to read testdata: %v", err)
}

pkgs, imgVersion, err := Parse(string(content))
if err != nil {
t.Errorf("Error while parsing the output: %v", err)
}

if imgVersion != "10.0.17763.3406" {
t.Errorf("Parse, ImageVersion: Got: %v, Want: %v", imgVersion, "10.0.17763.3406")
}

want := []DismPkg{
DismPkg{
PackageIdentity: "Microsoft-Windows-FodMetadata-Package~31bf3856ad364e35~amd64~~10.0.17763.1",
PackageVersion: "10.0.17763.1",
State: "Installed",
ReleaseType: "Feature Pack",
InstallTime: "9/15/2018 9:08 AM",
},
DismPkg{
PackageIdentity: "Package_for_KB4470788~31bf3856ad364e35~amd64~~17763.164.1.1",
PackageVersion: "17763.164.1.1",
State: "Installed",
ReleaseType: "Security Update",
InstallTime: "3/12/2019 6:27 AM",
},
DismPkg{
PackageIdentity: "Package_for_RollupFix~31bf3856ad364e35~amd64~~17763.3406.1.5",
PackageVersion: "17763.3406.1.5",
State: "Installed",
ReleaseType: "Security Update",
InstallTime: "9/13/2022 11:06 PM",
},
DismPkg{
PackageIdentity: "Package_for_RollupFix~31bf3856ad364e35~amd64~~17763.379.1.11",
PackageVersion: "17763.379.1.11",
State: "Superseded",
ReleaseType: "Security Update",
InstallTime: "3/12/2019 6:31 AM",
},
DismPkg{
PackageIdentity: "Package_for_ServicingStack_3232~31bf3856ad364e35~amd64~~17763.3232.1.1",
PackageVersion: "17763.3232.1.1",
State: "Installed",
ReleaseType: "Update",
InstallTime: "9/13/2022 10:46 PM",
},
}

if diff := cmp.Diff(want, pkgs); diff != "" {
t.Errorf("Parse: Diff = %v", diff)
}
}

func TestFindVersion(t *testing.T) {
type test struct {
input string
want string
}

tests := []test{
{
input: "Microsoft-Windows-FodMetadata-Package~31bf3856ad364e35~amd64~~10.0.17763.1",
want: "10.0.17763.1",
},
}

for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
got := findVersion(tt.input)
if got != tt.want {
t.Errorf("findVersion: Got: %v, Want: %v", got, tt.want)
}
})
}
}

func TestParseError(t *testing.T) {
content, err := os.ReadFile("testdata/err_testdata.txt")
if err != nil {
t.Fatalf("Failed to read testdata: %v", err)
}

_, _, err = Parse(string(content))
if err == nil || err.Error() != "Could not parse DISM output successfully" {
t.Errorf("Parse: Want: %v, Got: %v", "Could not parse DISM output successfully", err)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
Deployment Image Servicing and Management tool
Version: 10.0.20348.681

Image Version: 10.0.17763.3406

Package Identity : Microsoft-Windows-FodMetadata-Package~31bf3856ad364e35~amd64~~10.0.17763.1
State : Installed
Release Type : Feature Pack
Install Time : 9/15/2018 9:08 AM

Package Identity : Package_for_KB4470788~31bf3856ad364e35~amd64~~17763.164.1.1
State : Installed
Release Type : Security Update
Install Time : 3/12/2019 6:27 AM

Package Identity : Package_for_RollupFix~31bf3856ad364e35~amd64~~17763.3406.1.5
State : Installed
Release Type : Security Update
Install Time : 9/13/2022 11:06 PM

Package Identity : Package_for_RollupFix~31bf3856ad364e35~amd64~~17763.379.1.11
State : Superseded
Release Type : Security Update
Install Time : 3/12/2019 6:31 AM

Package Identity : Package_for_ServicingStack_3232~31bf3856ad364e35~amd64~~17763.3232.1.1
State : Installed
Release Type : Update
Install Time : 9/13/2022 10:46 PM

The operation completed successfully.
Loading

0 comments on commit 0f9acb4

Please sign in to comment.