mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Add SHA512 to merkle tree library
PiperOrigin-RevId: 339377254
This commit is contained in:
@@ -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
|
||||
)
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user