CVE-2022-29173 (Go, published 2022-05-05T23:15Z)
Description: value: go-tuf is a Go implementation of The Update Framework (TUF). go-tuf does not correctly implement the client workflow for updating the metadata files for roles other than the root role. Specifically, checks for rollback attacks are not implemented correctly meaning an attacker can cause clients to install software that is older than the software which the client previously knew to be available, and may include software with known vulnerabilities. In more detail, the client code of go-tuf has several issues in regards to preventing rollback attacks: 1. It does not take into account the content of any previously trusted metadata, if available, before proceeding with updating roles other than the root role (i.e., steps 5.4.3.1 and 5.5.5 of the detailed client workflow). This means that any form of version verification done on the newly-downloaded metadata is made using the default value of zero, which always passes. 2. For both timestamp and snapshot roles, go-tuf saves these metadata files as trusted before verifying if the version of the metafiles they refer to is correct (i.e., steps 5.5.4 and 5.6.4 of the detailed client workflow). A fix is available in version 0.3.0 or newer. No workarounds are known for this issue apart from upgrading.
Severity: MEDIUM (CVSS v3 base score: 8.8)
Weakness: CWE-354 \u2014 Improper Validation of Integrity Check Value
The product does not validate or incorrectly validates the integrity check values or "checksums" of a message. This may prevent it from detecting if the data has been modified or corrupted in transmission.
Repository: https://github.com/theupdateframework/go-tuf
Files changed:
client/client.go
client/errors.go
client/interop_test.go
cmd/tuf-client/get.go
cmd/tuf-client/list.go
util/util.go
verify/errors.go
verify/verify.go
Commit message: Merge pull request from GHSA-66x3-6cw3-v5gj

* Remove obsolete snapshot error check

Signed-off-by: Radoslav Dimitrov <dimitrovr@vmware.com>

* Add a dedicated error for missing target metadata file

Signed-off-by: Radoslav Dimitrov <dimitrovr@vmware.com>

* Fix protection against metadata rollback attacks

The go-tuf client now loads any previously trusted metadata before
proceeding with the update process. This is mandatory for the
protection against rollback attacks. It also fixes the detailed order
of operations necessary to implement such protection.

Signed-off-by: Radoslav Dimitrov <dimitrovr@vmware.com>

* Don't abort the update process if loading trusted metadata fails

Signed-off-by: Radoslav Dimitrov <dimitrovr@vmware.com>

* Update getLocalMeta so it tries loading every verified metadata file

If some of the metadata files fail to load, getLocalMeta will proceed
with trying to load the rest, but still return an error at the end,
if such occurred.

Signed-off-by: Radoslav Dimitrov <dimitrovr@vmware.com>

* Revert the preliminary targets.json download check

Signed-off-by: Radoslav Dimitrov <dimitrovr@vmware.com>

* Use current instead of old when addressing metadata

Signed-off-by: Radoslav Dimitrov <dimitrovr@vmware.com>

* Timestamp metadata do not require hashes and lenght being present

Signed-off-by: Radoslav Dimitrov <dimitrovr@vmware.com>

* fix: reload local meta based on the latest root

Clear the in-memory copy of the local metadata. The goal is to reload
and take into account only the metadata files that are verified by
the latest root. Otherwise, their content should be ignored.

Signed-off-by: Radoslav Dimitrov <dimitrovr@vmware.com>

* fix: update client unit tests for cases where metadata is now invalidated

Signed-off-by: Radoslav Dimitrov <dimitrovr@vmware.com>

* chore: clarify the case where targets rollback verification will be skipped

Signed-off-by: Radoslav Dimitrov <dimitrovr@vmware.com>

* chore: update getLocalMeta() description

Signed-off-by: Radoslav Dimitrov <dimitrovr@vmware.com>

* chore: simplify getLocalMeta() so it wraps the inner error upon failure

Signed-off-by: Radoslav Dimitrov <dimitrovr@vmware.com>

* chore: remove unused ErrLoadLocalFailed error type

Signed-off-by: Radoslav Dimitrov <dimitrovr@vmware.com>

* chore: improve code layout for decodeSnapshot()

Signed-off-by: Radoslav Dimitrov <dimitrovr@vmware.com>

Vulnerable code:
	// Get timestamp.json, extract snapshot.json file meta and save the
	// timestamp.json locally
	// Get snapshot.json, then extract file metas.
	// root.json meta should not be stored in the snapshot, if it is,
	// the root will be checked, re-downloaded

	// Save the snapshot.json
			return err
		c.timestampVer = timestamp.Version
			return err
		c.snapshotVer = snapshot.Version
			return err
		c.targetsVer = targets.Version
		// FIXME(TUF-0.9) temporarily support files with leading path separators.
		// c.targets = targets.Targets
		c.loadTargets(targets.Targets)

	c.localMeta = meta
	c.snapshotVer = snapshot.Version
	c.targetsVer = targets.Version
type ErrLatestSnapshot struct {
	Version int64
}

func (e ErrLatestSnapshot) Error() string {
	return fmt.Sprintf("tuf: the local snapshot version (%d) is the latest", e.Version)
}

func IsLatestSnapshot(err error) bool {
	_, ok := err.(ErrLatestSnapshot)
	return ok
}

	"strconv"
	l, _ := strconv.Atoi(stepName)
	c.Assert(files, HasLen, l+1)

	if _, err := client.Update(); err != nil && !tuf.IsLatestSnapshot(err) {
	if _, err := client.Update(); err != nil && !tuf.IsLatestSnapshot(err) {


	// As opposed to snapshots, the length and hashes are still required in
	// TUF-1.0. See:
	// https://github.com/theupdateframework/specification/issues/38
	if err := FileMetaEqual(actual.FileMeta, expected.FileMeta); err != nil {
		return err




Fixed code:
	// Load trusted metadata files, if any, and verify them against the latest root
	c.getLocalMeta()

	// 5.4.1 - Download the timestamp metadata
	// 5.4.(2,3 and 4) - Verify timestamp against various attacks
	// Returns the extracted snapshot metadata
	// 5.4.5 - Persist the timestamp metadata
	// 5.5.1 - Download snapshot metadata
	// 5.5.2 and 5.5.4 - Check against timestamp role's snapshot hash and version
	// 5.5.(3,5 and 6) - Verify snapshot against various attacks
	// Returns the extracted metadata files
	// 5.5.7 - Persist snapshot metadata
		// 5.6.1 - Download the top-level targets metadata file
		// 5.6.2 and 5.6.4 - Check against snapshot role's targets hash and version
		// 5.6.(3 and 5) - Verify signatures and check against freeze attack
		// 5.6.6 - Persist targets metadata
// Before trying to load the metadata files, it clears the in-memory copy of the local metadata.
// This is to insure that all of the loaded metadata files at the end are indeed verified by the latest root.
// If some of the metadata files fail to load it will proceed with trying to load the rest,
// but still return an error at the end, if such occurred. Otherwise returns nil.
	var retErr error
	loadFailed := false
	// Clear the in-memory copy of the local metadata. The goal is to reload and take into account
	// only the metadata files that are verified by the latest root. Otherwise, their content should
	// be ignored.
	c.localMeta = make(map[string]json.RawMessage)

	// Load the latest root meta
	// Load into memory the existing meta, if any, from the local storage
	// Verify the top-level metadata (timestamp, snapshot and targets) against the latest root and load it, if okay
			loadFailed = true
			retErr = err
		} else {
			c.localMeta["timestamp.json"] = meta["timestamp.json"]
			c.timestampVer = timestamp.Version
			loadFailed = true
			retErr = err
		} else {
			c.localMeta["snapshot.json"] = meta["snapshot.json"]
			c.snapshotVer = snapshot.Version
			loadFailed = true
			retErr = err
		} else {
			c.localMeta["targets.json"] = meta["targets.json"]
			c.targetsVer = targets.Version
			// FIXME(TUF-0.9) temporarily support files with leading path separators.
			// c.targets = targets.Targets
			c.loadTargets(targets.Targets)
	if loadFailed {
		// If any of the metadata failed to be verified, return the reason for that failure
		return retErr
	}
	// 5.6.2 and 5.6.4 - Check against snapshot role's targets hash and version
	// 5.5.2 and 5.5.4 - Check against timestamp role's snapshot hash and version
	// 5.5.(3 and 6) - Verify it's signed correctly and it's not expired
	// 5.5.5 - Check for top-level targets rollback attack
	// Verify explicitly that current targets meta version is less than or equal to the new one
	if snapshot.Meta["targets.json"].Version < c.targetsVer {
		return data.SnapshotFiles{}, verify.ErrLowVersion{Actual: snapshot.Meta["targets.json"].Version, Current: c.targetsVer}
	}

	// 5.5.5 - Get the local/trusted snapshot metadata, if any, and check all target metafiles against rollback attack
	// In case the local snapshot metadata was not verified by the keys in the latest root during getLocalMeta(),
	// snapshot.json won't be present in c.localMeta and thus this check will not be processed.
	if snapshotJSON, ok := c.localMeta["snapshot.json"]; ok {
		currentSnapshot := &data.Snapshot{}
		if err := c.db.UnmarshalTrusted(snapshotJSON, currentSnapshot, "snapshot"); err != nil {
			return data.SnapshotFiles{}, err
		}
		// 5.5.5 - Check for rollback attacks in both top-level and delegated targets roles (note that the Meta object includes both)
		for path, local := range currentSnapshot.Meta {
			if newMeta, ok := snapshot.Meta[path]; ok {
				// 5.5.5 - Check for rollback attack
				if newMeta.Version < local.Version {
					return data.SnapshotFiles{}, verify.ErrLowVersion{Actual: newMeta.Version, Current: local.Version}
				}
			} else {
				// 5.5.5 - Abort the update if a target file has been removed from the new snapshot file
				return data.SnapshotFiles{}, verify.ErrMissingTargetFile
			}
		}
	}
	// At this point we can trust the new snapshot, the top-level targets, and any delegated targets versions it refers to
	// so we can update the client's trusted versions and proceed with persisting the new snapshot metadata
	// c.snapshotVer was already set when we verified the timestamp metadata
	c.targetsVer = snapshot.Meta["targets.json"].Version
	// 5.6.(3 and 5) - Verify signatures and check against freeze attack
	// Generate a list with the updated targets
	// c.targetsVer was already updated when we verified the snapshot metadata
	// 5.4.3.2 - Check for snapshot rollback attack
	// Verify that the current snapshot meta version is less than or equal to the new one
	if timestamp.Meta["snapshot.json"].Version < c.snapshotVer {
		return data.TimestampFileMeta{}, verify.ErrLowVersion{Actual: timestamp.Meta["snapshot.json"].Version, Current: c.snapshotVer}
	}
	// At this point we can trust the new timestamp and the snaphost version it refers to
	// so we can update the client's trusted versions and proceed with persisting the new timestamp
	c.snapshotVer = timestamp.Meta["snapshot.json"].Version
	if stepName != "2" {
		// The rest of the test cases add one target file at a time for each cycle, so this is why we expect that
		// the number of updated targets returned by Update() should equals to 1
		c.Assert(files, HasLen, 1)
	} else {
		// The following test case (#2) verifies that when a targets key has been rotated in the latest 3.root.json,
		// the local targets.json meta is indeed ignored since it's signed with a key that has been now changed.
		// The reason we check for 3 here is that the updated targets corresponds to all target files listed in the
		// targets.json for test case #2
		c.Assert(files, HasLen, 3)
	}
	if _, err := client.Update(); err != nil {
	if _, err := client.Update(); err != nil {
	// 5.6.2 - Check against snapshot role's targets hash
	// 5.6.4 - Check against snapshot role's snapshot version
	// TUF no longer considers the length and hashes to be a required
	// member of Timestamp.
	if expected.Length != 0 && actual.Length != expected.Length {
		return ErrWrongLength{expected.Length, actual.Length}
	// 5.5.2 - Check against timestamp role's snapshot hash
	if len(expected.Hashes) != 0 {
		if err := hashEqual(actual.Hashes, expected.Hashes); err != nil {
			return err
		}
	}
	// 5.5.4 - Check against timestamp role's snapshot version
	ErrMissingTargetFile    = errors.New("tuf: missing previously listed targets metadata file")
	// Verify signatures and versions
	// Verify expiration