Repository: google/osv-scanner
Pull request #229 (2023-02-20 22:46:11)

ParseAs for dpkg-status
As previously done with apk `installed` lockfiles, I propose to add the parseAs option also for dpkg `status` files since they also have a very generic filename:

```bash
osv-scanner --lockfile 'dpkg-status:/var/lib/dpkg/status'
```


cmaritan: > You should add a CLI test like the one for `apk-installed`, to confirm it works

Done!

Patch:
diff --git a/cmd/osv-scanner/fixtures/locks-many/status b/cmd/osv-scanner/fixtures/locks-many/status
new file mode 100644
index 0000000..d17f8d4
--- /dev/null
+++ b/cmd/osv-scanner/fixtures/locks-many/status
@@ -0,0 +1,31 @@
+Package: adduser
+Status: install ok installed
+Priority: important
+Section: admin
+Installed-Size: 849
+Maintainer: redacted <redacted@predacted.com>
+Architecture: all
+Multi-Arch: foreign
+Version: 3.118
+Depends: passwd, debconf (>= 0.5) | debconf-2.0
+Suggests: liblocale-gettext-perl, perl
+Conffiles:
+ /etc/deluser.conf 773fb95e98a27947de4a95abb3d3f2a2
+Description: add and remove users and groups
+ This package includes the 'adduser' and 'deluser' commands for creating
+ and removing users.
+ .
+  - 'adduser' creates new users and groups and adds existing users to
+    existing groups;
+  - 'deluser' removes users and groups and removes users from a given
+    group.
+ .
+ Adding users with 'adduser' is much easier than adding them manually.
+ Adduser will choose appropriate UID and GID values, create a home
+ directory, copy skeletal user configuration, and automate setting
+ initial values for the user's password, real name and so on.
+ .
+ Deluser can back up and remove users' home directories
+ and mail spool or all the files they own on the system.
+ .
+ A custom script can be executed after each of the commands.
\ No newline at end of file
diff --git a/docs/_config.yml b/docs/_config.yml
index a887b39..f633c73 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -24,7 +24,7 @@ description: >- # this means to ignore newlines until "baseurl:"
 baseurl: "/osv-scanner" # the subpath of your site, e.g. /blog
 url: "" # the base hostname & protocol for your site, e.g. http://example.com
 github_username:  google
-ga_tracking: G-354127049
+ga_tracking: G-60SWD6J0TM
 
 # Build settings
 remote_theme: just-the-docs/just-the-docs
diff --git a/docs/working_docs/usage.md b/docs/working_docs/usage.md
index 86ea4ed..8b99aa1 100644
--- a/docs/working_docs/usage.md
+++ b/docs/working_docs/usage.md
@@ -80,11 +80,15 @@ A wide range of lockfiles are supported by utilizing this [lockfile package](htt
 - `requirements.txt`[\*](https://github.com/google/osv-scanner/issues/34)
 - `yarn.lock`
 
-The scanner also supports `installed` files used by the Alpine Package Keeper (apk) that typically live at `/lib/apk/db/installed`,
-however you must specify this explicitly using the `--lockfile` flag:
+The scanner also supports:
+- `installed` files used by the Alpine Package Keeper (apk) that typically live at `/lib/apk/db/installed`
+- `status` files used by the Debian Package manager (dpkg) that typically live at `/var/lib/dpkg/status`
+
+however you must specify them explicitly using the `--lockfile` flag:
 
 ```bash
 osv-scanner --lockfile 'apk-installed:/lib/apk/db/installed'
+osv-scanner --lockfile 'dpkg-status:/var/lib/dpkg/status'
 ```
 
 If the file you are scanning is located in a directory that has a colon in its name,
diff --git a/pkg/lockfile/dpkg-status.go b/pkg/lockfile/dpkg-status.go
index 4fec193..41cc872 100644
--- a/pkg/lockfile/dpkg-status.go
+++ b/pkg/lockfile/dpkg-status.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"os"
 	"regexp"
+	"sort"
 	"strings"
 )
 
@@ -160,3 +161,23 @@ func ParseDpkgStatus(pathToLockfile string) ([]PackageDetails, error) {
 
 	return packages, nil
 }
+
+// FromDpkgStatus attempts to parse the given file as an "dpkg-status" lockfile
+// used by the Debian Package (dpkg) to record installed packages.
+func FromDpkgStatus(pathToStatus string) (Lockfile, error) {
+	packages, err := ParseDpkgStatus(pathToStatus)
+
+	sort.Slice(packages, func(i, j int) bool {
+		if packages[i].Name == packages[j].Name {
+			return packages[i].Version < packages[j].Version
+		}
+
+		return packages[i].Name < packages[j].Name
+	})
+
+	return Lockfile{
+		FilePath: pathToStatus,
+		ParsedAs: "dpkg-status",
+		Packages: packages,
+	}, err
+}
diff --git a/pkg/osvscanner/osvscanner.go b/pkg/osvscanner/osvscanner.go
index 0a15841..5f4ad41 100644
--- a/pkg/osvscanner/osvscanner.go
+++ b/pkg/osvscanner/osvscanner.go
@@ -179,12 +179,15 @@ func scanLockfile(r *output.Reporter, query *osv.BatchedQuery, path string, pars
 	var err error
 	var parsedLockfile lockfile.Lockfile
 
-	// special case for the APK parser because it has a very generic name while
-	// living at a specific location, so it's not included in the map of parsers
+	// special case for the APK and DPKG parsers because they have a very generic name while
+	// living at a specific location, so they are not included in the map of parsers
 	// used by lockfile.Parse to avoid false-positives when scanning projects
-	if parseAs == "apk-installed" {
+	switch parseAs {
+	case "apk-installed":
 		parsedLockfile, err = lockfile.FromApkInstalled(path)
-	} else {
+	case "dpkg-status":
+		parsedLockfile, err = lockfile.FromDpkgStatus(path)
+	default:
 		parsedLockfile, err = lockfile.Parse(path, parseAs)
 	}
 


Test patch:
diff --git a/cmd/osv-scanner/main_test.go b/cmd/osv-scanner/main_test.go
index 7c6b40d..3199c9f 100644
--- a/cmd/osv-scanner/main_test.go
+++ b/cmd/osv-scanner/main_test.go
@@ -443,6 +443,20 @@ func TestRun_LockfileWithExplicitParseAs(t *testing.T) {
 			`,
 			wantStderr: "",
 		},
+		// "dpkg-status" is supported
+		{
+			name: "",
+			args: []string{
+				"",
+				"-L",
+				"dpkg-status:" + filepath.FromSlash("./fixtures/locks-many/status"),
+			},
+			wantExitCode: 0,
+			wantStdout: `
+				Scanned %%/fixtures/locks-many/status file as a dpkg-status and found 1 packages
+			`,
+			wantStderr: "",
+		},
 	}
 	for _, tt := range tests {
 		tt := tt