Add SHA512 to merkle tree library

PiperOrigin-RevId: 339377254
This commit is contained in:
Chong Cai
2020-10-27 19:12:35 -07:00
committed by gVisor bot
parent 24c33de748
commit bc91ae17f6
6 changed files with 370 additions and 201 deletions
+3
View File
@@ -121,6 +121,9 @@ const (
// Constants from uapi/linux/fsverity.h.
const (
FS_VERITY_HASH_ALG_SHA256 = 1
FS_VERITY_HASH_ALG_SHA512 = 2
FS_IOC_ENABLE_VERITY = 1082156677
FS_IOC_MEASURE_VERITY = 3221513862
)
+8 -2
View File
@@ -6,12 +6,18 @@ go_library(
name = "merkletree",
srcs = ["merkletree.go"],
visibility = ["//pkg/sentry:internal"],
deps = ["//pkg/usermem"],
deps = [
"//pkg/abi/linux",
"//pkg/usermem",
],
)
go_test(
name = "merkletree_test",
srcs = ["merkletree_test.go"],
library = ":merkletree",
deps = ["//pkg/usermem"],
deps = [
"//pkg/abi/linux",
"//pkg/usermem",
],
)
+81 -23
View File
@@ -18,21 +18,32 @@ package merkletree
import (
"bytes"
"crypto/sha256"
"crypto/sha512"
"fmt"
"io"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/usermem"
)
const (
// sha256DigestSize specifies the digest size of a SHA256 hash.
sha256DigestSize = 32
// sha512DigestSize specifies the digest size of a SHA512 hash.
sha512DigestSize = 64
)
// DigestSize returns the size (in bytes) of a digest.
// TODO(b/156980949): Allow config other hash methods (SHA384/SHA512).
func DigestSize() int {
return sha256DigestSize
// TODO(b/156980949): Allow config SHA384.
func DigestSize(hashAlgorithm int) int {
switch hashAlgorithm {
case linux.FS_VERITY_HASH_ALG_SHA256:
return sha256DigestSize
case linux.FS_VERITY_HASH_ALG_SHA512:
return sha512DigestSize
default:
return -1
}
}
// Layout defines the scale of a Merkle tree.
@@ -51,11 +62,19 @@ type Layout struct {
// InitLayout initializes and returns a new Layout object describing the structure
// of a tree. dataSize specifies the size of input data in bytes.
func InitLayout(dataSize int64, dataAndTreeInSameFile bool) Layout {
func InitLayout(dataSize int64, hashAlgorithms int, dataAndTreeInSameFile bool) (Layout, error) {
layout := Layout{
blockSize: usermem.PageSize,
// TODO(b/156980949): Allow config other hash methods (SHA384/SHA512).
digestSize: sha256DigestSize,
}
// TODO(b/156980949): Allow config SHA384.
switch hashAlgorithms {
case linux.FS_VERITY_HASH_ALG_SHA256:
layout.digestSize = sha256DigestSize
case linux.FS_VERITY_HASH_ALG_SHA512:
layout.digestSize = sha512DigestSize
default:
return Layout{}, fmt.Errorf("unexpected hash algorithms")
}
// treeStart is the offset (in bytes) of the first level of the tree in
@@ -88,7 +107,7 @@ func InitLayout(dataSize int64, dataAndTreeInSameFile bool) Layout {
}
layout.levelOffset = append(layout.levelOffset, treeStart+offset*layout.blockSize)
return layout
return layout, nil
}
// hashesPerBlock() returns the number of digests in each block. For example,
@@ -139,12 +158,33 @@ func (d *VerityDescriptor) String() string {
}
// verify generates a hash from d, and compares it with expected.
func (d *VerityDescriptor) verify(expected []byte) error {
h := sha256.Sum256([]byte(d.String()))
func (d *VerityDescriptor) verify(expected []byte, hashAlgorithms int) error {
h, err := hashData([]byte(d.String()), hashAlgorithms)
if err != nil {
return err
}
if !bytes.Equal(h[:], expected) {
return fmt.Errorf("unexpected root hash")
}
return nil
}
// hashData hashes data and returns the result hash based on the hash
// algorithms.
func hashData(data []byte, hashAlgorithms int) ([]byte, error) {
var digest []byte
switch hashAlgorithms {
case linux.FS_VERITY_HASH_ALG_SHA256:
digestArray := sha256.Sum256(data)
digest = digestArray[:]
case linux.FS_VERITY_HASH_ALG_SHA512:
digestArray := sha512.Sum512(data)
digest = digestArray[:]
default:
return nil, fmt.Errorf("unexpected hash algorithms")
}
return digest, nil
}
// GenerateParams contains the parameters used to generate a Merkle tree.
@@ -161,6 +201,8 @@ type GenerateParams struct {
UID uint32
// GID is the group ID of the target file.
GID uint32
// HashAlgorithms is the algorithms used to hash data.
HashAlgorithms int
// TreeReader is a reader for the Merkle tree.
TreeReader io.ReaderAt
// TreeWriter is a writer for the Merkle tree.
@@ -176,7 +218,10 @@ type GenerateParams struct {
// Generate returns a hash of a VerityDescriptor, which contains the file
// metadata and the hash from file content.
func Generate(params *GenerateParams) ([]byte, error) {
layout := InitLayout(params.Size, params.DataAndTreeInSameFile)
layout, err := InitLayout(params.Size, params.HashAlgorithms, params.DataAndTreeInSameFile)
if err != nil {
return nil, err
}
numBlocks := (params.Size + layout.blockSize - 1) / layout.blockSize
@@ -218,10 +263,13 @@ func Generate(params *GenerateParams) ([]byte, error) {
return nil, err
}
// Hash the bytes in buf.
digest := sha256.Sum256(buf)
digest, err := hashData(buf, params.HashAlgorithms)
if err != nil {
return nil, err
}
if level == layout.rootLevel() {
root = digest[:]
root = digest
}
// Write the generated hash to the end of the tree file.
@@ -246,8 +294,7 @@ func Generate(params *GenerateParams) ([]byte, error) {
GID: params.GID,
RootHash: root,
}
ret := sha256.Sum256([]byte(descriptor.String()))
return ret[:], nil
return hashData([]byte(descriptor.String()), params.HashAlgorithms)
}
// VerifyParams contains the params used to verify a portion of a file against
@@ -269,6 +316,8 @@ type VerifyParams struct {
UID uint32
// GID is the group ID of the target file.
GID uint32
// HashAlgorithms is the algorithms used to hash data.
HashAlgorithms int
// ReadOffset is the offset of the data range to be verified.
ReadOffset int64
// ReadSize is the size of the data range to be verified.
@@ -298,7 +347,7 @@ func verifyMetadata(params *VerifyParams, layout *Layout) error {
GID: params.GID,
RootHash: root,
}
return descriptor.verify(params.Expected)
return descriptor.verify(params.Expected, params.HashAlgorithms)
}
// Verify verifies the content read from data with offset. The content is
@@ -313,7 +362,10 @@ func Verify(params *VerifyParams) (int64, error) {
if params.ReadSize < 0 {
return 0, fmt.Errorf("unexpected read size: %d", params.ReadSize)
}
layout := InitLayout(int64(params.Size), params.DataAndTreeInSameFile)
layout, err := InitLayout(int64(params.Size), params.HashAlgorithms, params.DataAndTreeInSameFile)
if err != nil {
return 0, err
}
if params.ReadSize == 0 {
return 0, verifyMetadata(params, &layout)
}
@@ -354,7 +406,7 @@ func Verify(params *VerifyParams) (int64, error) {
UID: params.UID,
GID: params.GID,
}
if err := verifyBlock(params.Tree, &descriptor, &layout, buf, i, params.Expected); err != nil {
if err := verifyBlock(params.Tree, &descriptor, &layout, buf, i, params.HashAlgorithms, params.Expected); err != nil {
return 0, err
}
@@ -395,7 +447,7 @@ func Verify(params *VerifyParams) (int64, error) {
// fails if the calculated hash from block is different from any level of
// hashes stored in tree. And the final root hash is compared with
// expected.
func verifyBlock(tree io.ReaderAt, descriptor *VerityDescriptor, layout *Layout, dataBlock []byte, blockIndex int64, expected []byte) error {
func verifyBlock(tree io.ReaderAt, descriptor *VerityDescriptor, layout *Layout, dataBlock []byte, blockIndex int64, hashAlgorithms int, expected []byte) error {
if len(dataBlock) != int(layout.blockSize) {
return fmt.Errorf("incorrect block size")
}
@@ -406,8 +458,11 @@ func verifyBlock(tree io.ReaderAt, descriptor *VerityDescriptor, layout *Layout,
for level := 0; level < layout.numLevels(); level++ {
// Calculate hash.
if level == 0 {
digestArray := sha256.Sum256(dataBlock)
digest = digestArray[:]
h, err := hashData(dataBlock, hashAlgorithms)
if err != nil {
return err
}
digest = h
} else {
// Read a block in previous level that contains the
// hash we just generated, and generate a next level
@@ -415,8 +470,11 @@ func verifyBlock(tree io.ReaderAt, descriptor *VerityDescriptor, layout *Layout,
if _, err := tree.ReadAt(treeBlock, layout.blockOffset(level-1, blockIndex)); err != nil {
return err
}
digestArray := sha256.Sum256(treeBlock)
digest = digestArray[:]
h, err := hashData(treeBlock, hashAlgorithms)
if err != nil {
return err
}
digest = h
}
// Read the digest for the current block and store in
@@ -434,5 +492,5 @@ func verifyBlock(tree io.ReaderAt, descriptor *VerityDescriptor, layout *Layout,
// Verification for the tree succeeded. Now hash the descriptor with
// the root hash and compare it with expected.
descriptor.RootHash = digest
return descriptor.verify(expected)
return descriptor.verify(expected, hashAlgorithms)
}
+245 -151
View File
@@ -22,54 +22,114 @@ import (
"testing"
"time"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/usermem"
)
func TestLayout(t *testing.T) {
testCases := []struct {
dataSize int64
hashAlgorithms int
dataAndTreeInSameFile bool
expectedDigestSize int64
expectedLevelOffset []int64
}{
{
dataSize: 100,
hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256,
dataAndTreeInSameFile: false,
expectedDigestSize: 32,
expectedLevelOffset: []int64{0},
},
{
dataSize: 100,
hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512,
dataAndTreeInSameFile: false,
expectedDigestSize: 64,
expectedLevelOffset: []int64{0},
},
{
dataSize: 100,
hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256,
dataAndTreeInSameFile: true,
expectedDigestSize: 32,
expectedLevelOffset: []int64{usermem.PageSize},
},
{
dataSize: 100,
hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512,
dataAndTreeInSameFile: true,
expectedDigestSize: 64,
expectedLevelOffset: []int64{usermem.PageSize},
},
{
dataSize: 1000000,
hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256,
dataAndTreeInSameFile: false,
expectedDigestSize: 32,
expectedLevelOffset: []int64{0, 2 * usermem.PageSize, 3 * usermem.PageSize},
},
{
dataSize: 1000000,
hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512,
dataAndTreeInSameFile: false,
expectedDigestSize: 64,
expectedLevelOffset: []int64{0, 4 * usermem.PageSize, 5 * usermem.PageSize},
},
{
dataSize: 1000000,
hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256,
dataAndTreeInSameFile: true,
expectedDigestSize: 32,
expectedLevelOffset: []int64{245 * usermem.PageSize, 247 * usermem.PageSize, 248 * usermem.PageSize},
},
{
dataSize: 1000000,
hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512,
dataAndTreeInSameFile: true,
expectedDigestSize: 64,
expectedLevelOffset: []int64{245 * usermem.PageSize, 249 * usermem.PageSize, 250 * usermem.PageSize},
},
{
dataSize: 4096 * int64(usermem.PageSize),
hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256,
dataAndTreeInSameFile: false,
expectedDigestSize: 32,
expectedLevelOffset: []int64{0, 32 * usermem.PageSize, 33 * usermem.PageSize},
},
{
dataSize: 4096 * int64(usermem.PageSize),
hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512,
dataAndTreeInSameFile: false,
expectedDigestSize: 64,
expectedLevelOffset: []int64{0, 64 * usermem.PageSize, 65 * usermem.PageSize},
},
{
dataSize: 4096 * int64(usermem.PageSize),
hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256,
dataAndTreeInSameFile: true,
expectedDigestSize: 32,
expectedLevelOffset: []int64{4096 * usermem.PageSize, 4128 * usermem.PageSize, 4129 * usermem.PageSize},
},
{
dataSize: 4096 * int64(usermem.PageSize),
hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512,
dataAndTreeInSameFile: true,
expectedDigestSize: 64,
expectedLevelOffset: []int64{4096 * usermem.PageSize, 4160 * usermem.PageSize, 4161 * usermem.PageSize},
},
}
for _, tc := range testCases {
t.Run(fmt.Sprintf("%d", tc.dataSize), func(t *testing.T) {
l := InitLayout(tc.dataSize, tc.dataAndTreeInSameFile)
l, err := InitLayout(tc.dataSize, tc.hashAlgorithms, tc.dataAndTreeInSameFile)
if err != nil {
t.Fatalf("Failed to InitLayout: %v", err)
}
if l.blockSize != int64(usermem.PageSize) {
t.Errorf("Got blockSize %d, want %d", l.blockSize, usermem.PageSize)
}
if l.digestSize != sha256DigestSize {
if l.digestSize != tc.expectedDigestSize {
t.Errorf("Got digestSize %d, want %d", l.digestSize, sha256DigestSize)
}
if l.numLevels() != len(tc.expectedLevelOffset) {
@@ -118,24 +178,49 @@ func TestGenerate(t *testing.T) {
// The input data has size dataSize. It starts with the data in startWith,
// and all other bytes are zeroes.
testCases := []struct {
data []byte
expectedHash []byte
data []byte
hashAlgorithms int
expectedHash []byte
}{
{
data: bytes.Repeat([]byte{0}, usermem.PageSize),
expectedHash: []byte{64, 253, 58, 72, 192, 131, 82, 184, 193, 33, 108, 142, 43, 46, 179, 134, 244, 21, 29, 190, 14, 39, 66, 129, 6, 46, 200, 211, 30, 247, 191, 252},
data: bytes.Repeat([]byte{0}, usermem.PageSize),
hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256,
expectedHash: []byte{64, 253, 58, 72, 192, 131, 82, 184, 193, 33, 108, 142, 43, 46, 179, 134, 244, 21, 29, 190, 14, 39, 66, 129, 6, 46, 200, 211, 30, 247, 191, 252},
},
{
data: bytes.Repeat([]byte{0}, 128*usermem.PageSize+1),
expectedHash: []byte{182, 223, 218, 62, 65, 185, 160, 219, 93, 119, 186, 88, 205, 32, 122, 231, 173, 72, 78, 76, 65, 57, 177, 146, 159, 39, 44, 123, 230, 156, 97, 26},
data: bytes.Repeat([]byte{0}, usermem.PageSize),
hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512,
expectedHash: []byte{14, 27, 126, 158, 9, 94, 163, 51, 243, 162, 82, 167, 183, 127, 93, 121, 221, 23, 184, 59, 104, 166, 111, 49, 161, 195, 229, 111, 121, 201, 233, 68, 10, 154, 78, 142, 154, 236, 170, 156, 110, 167, 15, 144, 155, 97, 241, 235, 202, 233, 246, 217, 138, 88, 152, 179, 238, 46, 247, 185, 125, 20, 101, 201},
},
{
data: []byte{'a'},
expectedHash: []byte{28, 201, 8, 36, 150, 178, 111, 5, 193, 212, 129, 205, 206, 124, 211, 90, 224, 142, 81, 183, 72, 165, 243, 240, 242, 241, 76, 127, 101, 61, 63, 11},
data: bytes.Repeat([]byte{0}, 128*usermem.PageSize+1),
hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256,
expectedHash: []byte{182, 223, 218, 62, 65, 185, 160, 219, 93, 119, 186, 88, 205, 32, 122, 231, 173, 72, 78, 76, 65, 57, 177, 146, 159, 39, 44, 123, 230, 156, 97, 26},
},
{
data: bytes.Repeat([]byte{'a'}, usermem.PageSize),
expectedHash: []byte{106, 58, 160, 152, 41, 68, 38, 108, 245, 74, 177, 84, 64, 193, 19, 176, 249, 86, 27, 193, 85, 164, 99, 240, 79, 104, 148, 222, 76, 46, 191, 79},
data: bytes.Repeat([]byte{0}, 128*usermem.PageSize+1),
hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512,
expectedHash: []byte{55, 204, 240, 1, 224, 252, 58, 131, 251, 174, 45, 140, 107, 57, 118, 11, 18, 236, 203, 204, 19, 59, 27, 196, 3, 78, 21, 7, 22, 98, 197, 128, 17, 128, 90, 122, 54, 83, 253, 108, 156, 67, 59, 229, 236, 241, 69, 88, 99, 44, 127, 109, 204, 183, 150, 232, 187, 57, 228, 137, 209, 235, 241, 172},
},
{
data: []byte{'a'},
hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256,
expectedHash: []byte{28, 201, 8, 36, 150, 178, 111, 5, 193, 212, 129, 205, 206, 124, 211, 90, 224, 142, 81, 183, 72, 165, 243, 240, 242, 241, 76, 127, 101, 61, 63, 11},
},
{
data: []byte{'a'},
hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512,
expectedHash: []byte{207, 233, 114, 94, 113, 212, 243, 160, 59, 232, 226, 77, 28, 81, 176, 61, 211, 213, 222, 190, 148, 196, 90, 166, 237, 56, 113, 148, 230, 154, 23, 105, 14, 97, 144, 211, 12, 122, 226, 207, 167, 203, 136, 193, 38, 249, 227, 187, 92, 238, 101, 97, 170, 255, 246, 209, 246, 98, 241, 150, 175, 253, 173, 206},
},
{
data: bytes.Repeat([]byte{'a'}, usermem.PageSize),
hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256,
expectedHash: []byte{106, 58, 160, 152, 41, 68, 38, 108, 245, 74, 177, 84, 64, 193, 19, 176, 249, 86, 27, 193, 85, 164, 99, 240, 79, 104, 148, 222, 76, 46, 191, 79},
},
{
data: bytes.Repeat([]byte{'a'}, usermem.PageSize),
hashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA512,
expectedHash: []byte{110, 103, 29, 250, 27, 211, 235, 119, 112, 65, 49, 156, 6, 92, 66, 105, 133, 1, 187, 172, 169, 13, 186, 34, 105, 72, 252, 131, 12, 159, 91, 188, 79, 184, 240, 227, 40, 164, 72, 193, 65, 31, 227, 153, 191, 6, 117, 42, 82, 122, 33, 255, 92, 215, 215, 249, 2, 131, 170, 134, 39, 192, 222, 33},
},
}
@@ -149,6 +234,7 @@ func TestGenerate(t *testing.T) {
Mode: defaultMode,
UID: defaultUID,
GID: defaultGID,
HashAlgorithms: tc.hashAlgorithms,
TreeReader: &tree,
TreeWriter: &tree,
DataAndTreeInSameFile: dataAndTreeInSameFile,
@@ -348,77 +434,81 @@ func TestVerify(t *testing.T) {
// Generate random bytes in data.
rand.Read(data)
for _, dataAndTreeInSameFile := range []bool{false, true} {
var tree bytesReadWriter
genParams := GenerateParams{
Size: int64(len(data)),
Name: defaultName,
Mode: defaultMode,
UID: defaultUID,
GID: defaultGID,
TreeReader: &tree,
TreeWriter: &tree,
DataAndTreeInSameFile: dataAndTreeInSameFile,
}
if dataAndTreeInSameFile {
tree.Write(data)
genParams.File = &tree
} else {
genParams.File = &bytesReadWriter{
bytes: data,
for _, hashAlgorithms := range []int{linux.FS_VERITY_HASH_ALG_SHA256, linux.FS_VERITY_HASH_ALG_SHA512} {
for _, dataAndTreeInSameFile := range []bool{false, true} {
var tree bytesReadWriter
genParams := GenerateParams{
Size: int64(len(data)),
Name: defaultName,
Mode: defaultMode,
UID: defaultUID,
GID: defaultGID,
HashAlgorithms: hashAlgorithms,
TreeReader: &tree,
TreeWriter: &tree,
DataAndTreeInSameFile: dataAndTreeInSameFile,
}
if dataAndTreeInSameFile {
tree.Write(data)
genParams.File = &tree
} else {
genParams.File = &bytesReadWriter{
bytes: data,
}
}
hash, err := Generate(&genParams)
if err != nil {
t.Fatalf("Generate failed: %v", err)
}
}
hash, err := Generate(&genParams)
if err != nil {
t.Fatalf("Generate failed: %v", err)
}
// Flip a bit in data and checks Verify results.
var buf bytes.Buffer
data[tc.modifyByte] ^= 1
verifyParams := VerifyParams{
Out: &buf,
File: bytes.NewReader(data),
Tree: &tree,
Size: tc.dataSize,
Name: defaultName,
Mode: defaultMode,
UID: defaultUID,
GID: defaultGID,
ReadOffset: tc.verifyStart,
ReadSize: tc.verifySize,
Expected: hash,
DataAndTreeInSameFile: dataAndTreeInSameFile,
}
if tc.modifyName {
verifyParams.Name = defaultName + "abc"
}
if tc.modifyMode {
verifyParams.Mode = defaultMode + 1
}
if tc.modifyUID {
verifyParams.UID = defaultUID + 1
}
if tc.modifyGID {
verifyParams.GID = defaultGID + 1
}
if tc.shouldSucceed {
n, err := Verify(&verifyParams)
if err != nil && err != io.EOF {
t.Errorf("Verification failed when expected to succeed: %v", err)
// Flip a bit in data and checks Verify results.
var buf bytes.Buffer
data[tc.modifyByte] ^= 1
verifyParams := VerifyParams{
Out: &buf,
File: bytes.NewReader(data),
Tree: &tree,
Size: tc.dataSize,
Name: defaultName,
Mode: defaultMode,
UID: defaultUID,
GID: defaultGID,
HashAlgorithms: hashAlgorithms,
ReadOffset: tc.verifyStart,
ReadSize: tc.verifySize,
Expected: hash,
DataAndTreeInSameFile: dataAndTreeInSameFile,
}
if n != tc.verifySize {
t.Errorf("Got Verify output size %d, want %d", n, tc.verifySize)
if tc.modifyName {
verifyParams.Name = defaultName + "abc"
}
if int64(buf.Len()) != tc.verifySize {
t.Errorf("Got Verify output buf size %d, want %d,", buf.Len(), tc.verifySize)
if tc.modifyMode {
verifyParams.Mode = defaultMode + 1
}
if !bytes.Equal(data[tc.verifyStart:tc.verifyStart+tc.verifySize], buf.Bytes()) {
t.Errorf("Incorrect output buf from Verify")
if tc.modifyUID {
verifyParams.UID = defaultUID + 1
}
} else {
if _, err := Verify(&verifyParams); err == nil {
t.Errorf("Verification succeeded when expected to fail")
if tc.modifyGID {
verifyParams.GID = defaultGID + 1
}
if tc.shouldSucceed {
n, err := Verify(&verifyParams)
if err != nil && err != io.EOF {
t.Errorf("Verification failed when expected to succeed: %v", err)
}
if n != tc.verifySize {
t.Errorf("Got Verify output size %d, want %d", n, tc.verifySize)
}
if int64(buf.Len()) != tc.verifySize {
t.Errorf("Got Verify output buf size %d, want %d,", buf.Len(), tc.verifySize)
}
if !bytes.Equal(data[tc.verifyStart:tc.verifyStart+tc.verifySize], buf.Bytes()) {
t.Errorf("Incorrect output buf from Verify")
}
} else {
if _, err := Verify(&verifyParams); err == nil {
t.Errorf("Verification succeeded when expected to fail")
}
}
}
}
@@ -435,87 +525,91 @@ func TestVerifyRandom(t *testing.T) {
// Generate random bytes in data.
rand.Read(data)
for _, dataAndTreeInSameFile := range []bool{false, true} {
var tree bytesReadWriter
genParams := GenerateParams{
Size: int64(len(data)),
Name: defaultName,
Mode: defaultMode,
UID: defaultUID,
GID: defaultGID,
TreeReader: &tree,
TreeWriter: &tree,
DataAndTreeInSameFile: dataAndTreeInSameFile,
}
if dataAndTreeInSameFile {
tree.Write(data)
genParams.File = &tree
} else {
genParams.File = &bytesReadWriter{
bytes: data,
for _, hashAlgorithms := range []int{linux.FS_VERITY_HASH_ALG_SHA256, linux.FS_VERITY_HASH_ALG_SHA512} {
for _, dataAndTreeInSameFile := range []bool{false, true} {
var tree bytesReadWriter
genParams := GenerateParams{
Size: int64(len(data)),
Name: defaultName,
Mode: defaultMode,
UID: defaultUID,
GID: defaultGID,
HashAlgorithms: hashAlgorithms,
TreeReader: &tree,
TreeWriter: &tree,
DataAndTreeInSameFile: dataAndTreeInSameFile,
}
}
hash, err := Generate(&genParams)
if err != nil {
t.Fatalf("Generate failed: %v", err)
}
// Pick a random portion of data.
start := rand.Int63n(dataSize - 1)
size := rand.Int63n(dataSize) + 1
if dataAndTreeInSameFile {
tree.Write(data)
genParams.File = &tree
} else {
genParams.File = &bytesReadWriter{
bytes: data,
}
}
hash, err := Generate(&genParams)
if err != nil {
t.Fatalf("Generate failed: %v", err)
}
var buf bytes.Buffer
verifyParams := VerifyParams{
Out: &buf,
File: bytes.NewReader(data),
Tree: &tree,
Size: dataSize,
Name: defaultName,
Mode: defaultMode,
UID: defaultUID,
GID: defaultGID,
ReadOffset: start,
ReadSize: size,
Expected: hash,
DataAndTreeInSameFile: dataAndTreeInSameFile,
}
// Pick a random portion of data.
start := rand.Int63n(dataSize - 1)
size := rand.Int63n(dataSize) + 1
// Checks that the random portion of data from the original data is
// verified successfully.
n, err := Verify(&verifyParams)
if err != nil && err != io.EOF {
t.Errorf("Verification failed for correct data: %v", err)
}
if size > dataSize-start {
size = dataSize - start
}
if n != size {
t.Errorf("Got Verify output size %d, want %d", n, size)
}
if int64(buf.Len()) != size {
t.Errorf("Got Verify output buf size %d, want %d", buf.Len(), size)
}
if !bytes.Equal(data[start:start+size], buf.Bytes()) {
t.Errorf("Incorrect output buf from Verify")
}
var buf bytes.Buffer
verifyParams := VerifyParams{
Out: &buf,
File: bytes.NewReader(data),
Tree: &tree,
Size: dataSize,
Name: defaultName,
Mode: defaultMode,
UID: defaultUID,
GID: defaultGID,
HashAlgorithms: hashAlgorithms,
ReadOffset: start,
ReadSize: size,
Expected: hash,
DataAndTreeInSameFile: dataAndTreeInSameFile,
}
// Verify that modified metadata should fail verification.
buf.Reset()
verifyParams.Name = defaultName + "abc"
if _, err := Verify(&verifyParams); err == nil {
t.Error("Verify succeeded for modified metadata, expect failure")
}
// Checks that the random portion of data from the original data is
// verified successfully.
n, err := Verify(&verifyParams)
if err != nil && err != io.EOF {
t.Errorf("Verification failed for correct data: %v", err)
}
if size > dataSize-start {
size = dataSize - start
}
if n != size {
t.Errorf("Got Verify output size %d, want %d", n, size)
}
if int64(buf.Len()) != size {
t.Errorf("Got Verify output buf size %d, want %d", buf.Len(), size)
}
if !bytes.Equal(data[start:start+size], buf.Bytes()) {
t.Errorf("Incorrect output buf from Verify")
}
// Flip a random bit in randPortion, and check that verification fails.
buf.Reset()
randBytePos := rand.Int63n(size)
data[start+randBytePos] ^= 1
verifyParams.File = bytes.NewReader(data)
verifyParams.Name = defaultName
// Verify that modified metadata should fail verification.
buf.Reset()
verifyParams.Name = defaultName + "abc"
if _, err := Verify(&verifyParams); err == nil {
t.Error("Verify succeeded for modified metadata, expect failure")
}
if _, err := Verify(&verifyParams); err == nil {
t.Error("Verification succeeded for modified data, expect failure")
// Flip a random bit in randPortion, and check that verification fails.
buf.Reset()
randBytePos := rand.Int63n(size)
data[start+randBytePos] ^= 1
verifyParams.File = bytes.NewReader(data)
verifyParams.Name = defaultName
if _, err := Verify(&verifyParams); err == nil {
t.Error("Verification succeeded for modified data, expect failure")
}
}
}
}
+21 -17
View File
@@ -267,16 +267,18 @@ func (fs *filesystem) verifyChild(ctx context.Context, parent *dentry, child *de
// Verify returns with success.
var buf bytes.Buffer
if _, err := merkletree.Verify(&merkletree.VerifyParams{
Out: &buf,
File: &fdReader,
Tree: &fdReader,
Size: int64(parentSize),
Name: parent.name,
Mode: uint32(parentStat.Mode),
UID: parentStat.UID,
GID: parentStat.GID,
Out: &buf,
File: &fdReader,
Tree: &fdReader,
Size: int64(parentSize),
Name: parent.name,
Mode: uint32(parentStat.Mode),
UID: parentStat.UID,
GID: parentStat.GID,
//TODO(b/156980949): Support passing other hash algorithms.
HashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256,
ReadOffset: int64(offset),
ReadSize: int64(merkletree.DigestSize()),
ReadSize: int64(merkletree.DigestSize(linux.FS_VERITY_HASH_ALG_SHA256)),
Expected: parent.hash,
DataAndTreeInSameFile: true,
}); err != nil && err != io.EOF {
@@ -342,14 +344,16 @@ func (fs *filesystem) verifyStat(ctx context.Context, d *dentry, stat linux.Stat
var buf bytes.Buffer
params := &merkletree.VerifyParams{
Out: &buf,
Tree: &fdReader,
Size: int64(size),
Name: d.name,
Mode: uint32(stat.Mode),
UID: stat.UID,
GID: stat.GID,
ReadOffset: 0,
Out: &buf,
Tree: &fdReader,
Size: int64(size),
Name: d.name,
Mode: uint32(stat.Mode),
UID: stat.UID,
GID: stat.GID,
//TODO(b/156980949): Support passing other hash algorithms.
HashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256,
ReadOffset: 0,
// Set read size to 0 so only the metadata is verified.
ReadSize: 0,
Expected: d.hash,
+12 -8
View File
@@ -600,6 +600,8 @@ func (fd *fileDescription) generateMerkle(ctx context.Context) ([]byte, uint64,
params := &merkletree.GenerateParams{
TreeReader: &merkleReader,
TreeWriter: &merkleWriter,
//TODO(b/156980949): Support passing other hash algorithms.
HashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256,
}
switch atomic.LoadUint32(&fd.d.mode) & linux.S_IFMT {
@@ -836,14 +838,16 @@ func (fd *fileDescription) PRead(ctx context.Context, dst usermem.IOSequence, of
}
n, err := merkletree.Verify(&merkletree.VerifyParams{
Out: dst.Writer(ctx),
File: &dataReader,
Tree: &merkleReader,
Size: int64(size),
Name: fd.d.name,
Mode: fd.d.mode,
UID: fd.d.uid,
GID: fd.d.gid,
Out: dst.Writer(ctx),
File: &dataReader,
Tree: &merkleReader,
Size: int64(size),
Name: fd.d.name,
Mode: fd.d.mode,
UID: fd.d.uid,
GID: fd.d.gid,
//TODO(b/156980949): Support passing other hash algorithms.
HashAlgorithms: linux.FS_VERITY_HASH_ALG_SHA256,
ReadOffset: offset,
ReadSize: dst.NumBytes(),
Expected: fd.d.hash,