Files
rclone/cmd/selfupdate/verify_test.go
T
Nick Craig-Wood 672bbf75ec selfupdate: fix hash check trusting unsigned data in the signed SHA256SUMS GHSA-w33h-3qgq-8mv8 CVE-PENDING
rclone selfupdate checked the PGP signature on the clearsigned block
of SHA256SUMS but then looked up the archive hash in the whole
downloaded file. Unsigned data can appear in that file without
invalidating the signature. A hash line placed before or after the
signed text was accepted, allowing whoever controlled the download to
install an arbitrary unsigned archive.

The hash is now only looked up in the signed text.

Affects stable (non --beta) selfupdate since v1.55.0. --beta updates
are not signature checked.
2026-10-08 22:49:33 +01:00

62 lines
2.2 KiB
Go

//go:build !noselfupdate
package selfupdate
import (
"context"
"encoding/hex"
"os"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestVerify(t *testing.T) {
ctx := context.Background()
sumsBuf, err := os.ReadFile("testdata/verify/SHA256SUMS")
require.NoError(t, err)
hash, err := hex.DecodeString("b20b47f579a2c790ca752fb5d8e5651fade7d5867cbac0a4f71e805fc5c468d0")
require.NoError(t, err)
t.Run("NoError", func(t *testing.T) {
err = verifyHashsumDownloaded(ctx, sumsBuf, "archive.zip", hash)
require.NoError(t, err)
})
t.Run("BadSig", func(t *testing.T) {
sumsBuf[0x60] ^= 1 // change the signature by one bit
err = verifyHashsumDownloaded(ctx, sumsBuf, "archive.zip", hash)
assert.ErrorContains(t, err, "invalid signature")
sumsBuf[0x60] ^= 1 // undo the change
})
t.Run("BadSum", func(t *testing.T) {
hash[0] ^= 1 // change the SHA256 by one bit
err = verifyHashsumDownloaded(ctx, sumsBuf, "archive.zip", hash)
assert.ErrorContains(t, err, "archive hash mismatch")
hash[0] ^= 1 // undo the change
})
t.Run("BadName", func(t *testing.T) {
err = verifyHashsumDownloaded(ctx, sumsBuf, "archive.zipX", hash)
assert.ErrorContains(t, err, "unable to find hash")
})
t.Run("UnsignedPrefix", func(t *testing.T) {
hash[0] ^= 1 // forge an unsigned hash line before the signed block
prefixed := append([]byte(hex.EncodeToString(hash)+" archive.zip\n"), sumsBuf...)
err = verifyHashsumDownloaded(ctx, prefixed, "archive.zip", hash)
assert.ErrorContains(t, err, "archive hash mismatch")
hash[0] ^= 1 // undo the change
})
t.Run("UnsignedInArmor", func(t *testing.T) {
// Dash escaping the signed line doesn't change the signed text but hides
// it from a raw scan, and lines after the armor checksum are ignored.
badHash := hex.EncodeToString([]byte{0x01})
armored := strings.Replace(string(sumsBuf), "b20b", "- b20b", 1)
armored = strings.Replace(armored, "=1GTr\n", "=1GTr\n"+badHash+" archive.zip\n", 1)
err = verifyHashsumDownloaded(ctx, []byte(armored), "archive.zip", []byte{0x01})
assert.ErrorContains(t, err, "archive hash mismatch")
err = verifyHashsumDownloaded(ctx, []byte(armored), "archive.zip", hash)
require.NoError(t, err)
})
}