mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Add consistent precondition formatting for verity
Also add the lock order for verity fs, and add a lock to protect dentry hash. PiperOrigin-RevId: 342946537
This commit is contained in:
@@ -107,8 +107,10 @@ func (fs *filesystem) renameMuUnlockAndCheckDrop(ctx context.Context, ds **[]*de
|
||||
// Dentries which may have a reference count of zero, and which therefore
|
||||
// should be dropped once traversal is complete, are appended to ds.
|
||||
//
|
||||
// Preconditions: fs.renameMu must be locked. d.dirMu must be locked.
|
||||
// !rp.Done().
|
||||
// Preconditions:
|
||||
// * fs.renameMu must be locked.
|
||||
// * d.dirMu must be locked.
|
||||
// * !rp.Done().
|
||||
func (fs *filesystem) stepLocked(ctx context.Context, rp *vfs.ResolvingPath, d *dentry, mayFollowSymlinks bool, ds **[]*dentry) (*dentry, error) {
|
||||
if !d.isDir() {
|
||||
return nil, syserror.ENOTDIR
|
||||
@@ -158,15 +160,19 @@ afterSymlink:
|
||||
return child, nil
|
||||
}
|
||||
|
||||
// verifyChild verifies the hash of child against the already verified hash of
|
||||
// the parent to ensure the child is expected. verifyChild triggers a sentry
|
||||
// panic if unexpected modifications to the file system are detected. In
|
||||
// verifyChildLocked verifies the hash of child against the already verified
|
||||
// hash of the parent to ensure the child is expected. verifyChild triggers a
|
||||
// sentry panic if unexpected modifications to the file system are detected. In
|
||||
// noCrashOnVerificationFailure mode it returns a syserror instead.
|
||||
// Preconditions: fs.renameMu must be locked. d.dirMu must be locked.
|
||||
//
|
||||
// Preconditions:
|
||||
// * fs.renameMu must be locked.
|
||||
// * d.dirMu must be locked.
|
||||
//
|
||||
// TODO(b/166474175): Investigate all possible errors returned in this
|
||||
// function, and make sure we differentiate all errors that indicate unexpected
|
||||
// modifications to the file system from the ones that are not harmful.
|
||||
func (fs *filesystem) verifyChild(ctx context.Context, parent *dentry, child *dentry) (*dentry, error) {
|
||||
func (fs *filesystem) verifyChildLocked(ctx context.Context, parent *dentry, child *dentry) (*dentry, error) {
|
||||
vfsObj := fs.vfsfs.VirtualFilesystem()
|
||||
|
||||
// Get the path to the child dentry. This is only used to provide path
|
||||
@@ -268,7 +274,8 @@ func (fs *filesystem) verifyChild(ctx context.Context, parent *dentry, child *de
|
||||
// contain the hash of the children in the parent Merkle tree when
|
||||
// Verify returns with success.
|
||||
var buf bytes.Buffer
|
||||
if _, err := merkletree.Verify(&merkletree.VerifyParams{
|
||||
parent.hashMu.RLock()
|
||||
_, err = merkletree.Verify(&merkletree.VerifyParams{
|
||||
Out: &buf,
|
||||
File: &fdReader,
|
||||
Tree: &fdReader,
|
||||
@@ -284,21 +291,27 @@ func (fs *filesystem) verifyChild(ctx context.Context, parent *dentry, child *de
|
||||
ReadSize: int64(merkletree.DigestSize(fs.alg.toLinuxHashAlg())),
|
||||
Expected: parent.hash,
|
||||
DataAndTreeInSameFile: true,
|
||||
}); err != nil && err != io.EOF {
|
||||
})
|
||||
parent.hashMu.RUnlock()
|
||||
if err != nil && err != io.EOF {
|
||||
return nil, alertIntegrityViolation(fmt.Sprintf("Verification for %s failed: %v", childPath, err))
|
||||
}
|
||||
|
||||
// Cache child hash when it's verified the first time.
|
||||
child.hashMu.Lock()
|
||||
if len(child.hash) == 0 {
|
||||
child.hash = buf.Bytes()
|
||||
}
|
||||
child.hashMu.Unlock()
|
||||
return child, nil
|
||||
}
|
||||
|
||||
// verifyStatAndChildren verifies the stat and children names against the
|
||||
// verifyStatAndChildrenLocked verifies the stat and children names against the
|
||||
// verified hash. The mode/uid/gid and childrenNames of the file is cached
|
||||
// after verified.
|
||||
func (fs *filesystem) verifyStatAndChildren(ctx context.Context, d *dentry, stat linux.Statx) error {
|
||||
//
|
||||
// Preconditions: d.dirMu must be locked.
|
||||
func (fs *filesystem) verifyStatAndChildrenLocked(ctx context.Context, d *dentry, stat linux.Statx) error {
|
||||
vfsObj := fs.vfsfs.VirtualFilesystem()
|
||||
|
||||
// Get the path to the child dentry. This is only used to provide path
|
||||
@@ -390,6 +403,7 @@ func (fs *filesystem) verifyStatAndChildren(ctx context.Context, d *dentry, stat
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
d.hashMu.RLock()
|
||||
params := &merkletree.VerifyParams{
|
||||
Out: &buf,
|
||||
Tree: &fdReader,
|
||||
@@ -407,6 +421,7 @@ func (fs *filesystem) verifyStatAndChildren(ctx context.Context, d *dentry, stat
|
||||
Expected: d.hash,
|
||||
DataAndTreeInSameFile: false,
|
||||
}
|
||||
d.hashMu.RUnlock()
|
||||
if atomic.LoadUint32(&d.mode)&linux.S_IFMT == linux.S_IFDIR {
|
||||
params.DataAndTreeInSameFile = true
|
||||
}
|
||||
@@ -421,7 +436,9 @@ func (fs *filesystem) verifyStatAndChildren(ctx context.Context, d *dentry, stat
|
||||
return nil
|
||||
}
|
||||
|
||||
// Preconditions: fs.renameMu must be locked. d.dirMu must be locked.
|
||||
// Preconditions:
|
||||
// * fs.renameMu must be locked.
|
||||
// * parent.dirMu must be locked.
|
||||
func (fs *filesystem) getChildLocked(ctx context.Context, parent *dentry, name string, ds **[]*dentry) (*dentry, error) {
|
||||
if child, ok := parent.children[name]; ok {
|
||||
// If verity is enabled on child, we should check again whether
|
||||
@@ -470,7 +487,7 @@ func (fs *filesystem) getChildLocked(ctx context.Context, parent *dentry, name s
|
||||
// be cached before enabled.
|
||||
if fs.allowRuntimeEnable {
|
||||
if parent.verityEnabled() {
|
||||
if _, err := fs.verifyChild(ctx, parent, child); err != nil {
|
||||
if _, err := fs.verifyChildLocked(ctx, parent, child); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -486,7 +503,7 @@ func (fs *filesystem) getChildLocked(ctx context.Context, parent *dentry, name s
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := fs.verifyStatAndChildren(ctx, child, stat); err != nil {
|
||||
if err := fs.verifyStatAndChildrenLocked(ctx, child, stat); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -506,7 +523,9 @@ func (fs *filesystem) getChildLocked(ctx context.Context, parent *dentry, name s
|
||||
return child, nil
|
||||
}
|
||||
|
||||
// Preconditions: fs.renameMu must be locked. parent.dirMu must be locked.
|
||||
// Preconditions:
|
||||
// * fs.renameMu must be locked.
|
||||
// * parent.dirMu must be locked.
|
||||
func (fs *filesystem) lookupAndVerifyLocked(ctx context.Context, parent *dentry, name string) (*dentry, error) {
|
||||
vfsObj := fs.vfsfs.VirtualFilesystem()
|
||||
|
||||
@@ -597,13 +616,13 @@ func (fs *filesystem) lookupAndVerifyLocked(ctx context.Context, parent *dentry,
|
||||
// allowRuntimeEnable mode and the parent directory hasn't been enabled
|
||||
// yet.
|
||||
if parent.verityEnabled() {
|
||||
if _, err := fs.verifyChild(ctx, parent, child); err != nil {
|
||||
if _, err := fs.verifyChildLocked(ctx, parent, child); err != nil {
|
||||
child.destroyLocked(ctx)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if child.verityEnabled() {
|
||||
if err := fs.verifyStatAndChildren(ctx, child, stat); err != nil {
|
||||
if err := fs.verifyStatAndChildrenLocked(ctx, child, stat); err != nil {
|
||||
child.destroyLocked(ctx)
|
||||
return nil, err
|
||||
}
|
||||
@@ -617,7 +636,9 @@ func (fs *filesystem) lookupAndVerifyLocked(ctx context.Context, parent *dentry,
|
||||
// rp.Start().Impl().(*dentry)). It does not check that the returned directory
|
||||
// is searchable by the provider of rp.
|
||||
//
|
||||
// Preconditions: fs.renameMu must be locked. !rp.Done().
|
||||
// Preconditions:
|
||||
// * fs.renameMu must be locked.
|
||||
// * !rp.Done().
|
||||
func (fs *filesystem) walkParentDirLocked(ctx context.Context, rp *vfs.ResolvingPath, d *dentry, ds **[]*dentry) (*dentry, error) {
|
||||
for !rp.Final() {
|
||||
d.dirMu.Lock()
|
||||
@@ -958,11 +979,13 @@ func (fs *filesystem) StatAt(ctx context.Context, rp *vfs.ResolvingPath, opts vf
|
||||
if err != nil {
|
||||
return linux.Statx{}, err
|
||||
}
|
||||
d.dirMu.Lock()
|
||||
if d.verityEnabled() {
|
||||
if err := fs.verifyStatAndChildren(ctx, d, stat); err != nil {
|
||||
if err := fs.verifyStatAndChildrenLocked(ctx, d, stat); err != nil {
|
||||
return linux.Statx{}, err
|
||||
}
|
||||
}
|
||||
d.dirMu.Unlock()
|
||||
return stat, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,18 @@
|
||||
// The verity file system is read-only, except for one case: when
|
||||
// allowRuntimeEnable is true, additional Merkle files can be generated using
|
||||
// the FS_IOC_ENABLE_VERITY ioctl.
|
||||
//
|
||||
// Lock order:
|
||||
//
|
||||
// filesystem.renameMu
|
||||
// dentry.dirMu
|
||||
// fileDescription.mu
|
||||
// filesystem.verityMu
|
||||
// dentry.hashMu
|
||||
//
|
||||
// Locking dentry.dirMu in multiple dentries requires that parent dentries are
|
||||
// locked before child dentries, and that filesystem.renameMu is locked to
|
||||
// stabilize this relationship.
|
||||
package verity
|
||||
|
||||
import (
|
||||
@@ -372,12 +384,14 @@ func (fstype FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt
|
||||
return nil, nil, alertIntegrityViolation(fmt.Sprintf("Failed to deserialize childrenNames: %v", err))
|
||||
}
|
||||
|
||||
if err := fs.verifyStatAndChildren(ctx, d, stat); err != nil {
|
||||
if err := fs.verifyStatAndChildrenLocked(ctx, d, stat); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
d.hashMu.Lock()
|
||||
copy(d.hash, iopts.RootHash)
|
||||
d.hashMu.Unlock()
|
||||
d.vfsd.Init(d)
|
||||
|
||||
fs.rootDentry = d
|
||||
@@ -402,7 +416,8 @@ type dentry struct {
|
||||
fs *filesystem
|
||||
|
||||
// mode, uid, gid and size are the file mode, owner, group, and size of
|
||||
// the file in the underlying file system.
|
||||
// the file in the underlying file system. They are set when a dentry
|
||||
// is initialized, and never modified.
|
||||
mode uint32
|
||||
uid uint32
|
||||
gid uint32
|
||||
@@ -425,18 +440,22 @@ type dentry struct {
|
||||
|
||||
// childrenNames stores the name of all children of the dentry. This is
|
||||
// used by verity to check whether a child is expected. This is only
|
||||
// populated by enableVerity.
|
||||
// populated by enableVerity. childrenNames is also protected by dirMu.
|
||||
childrenNames map[string]struct{}
|
||||
|
||||
// lowerVD is the VirtualDentry in the underlying file system.
|
||||
// lowerVD is the VirtualDentry in the underlying file system. It is
|
||||
// never modified after initialized.
|
||||
lowerVD vfs.VirtualDentry
|
||||
|
||||
// lowerMerkleVD is the VirtualDentry of the corresponding Merkle tree
|
||||
// in the underlying file system.
|
||||
// in the underlying file system. It is never modified after
|
||||
// initialized.
|
||||
lowerMerkleVD vfs.VirtualDentry
|
||||
|
||||
// hash is the calculated hash for the current file or directory.
|
||||
hash []byte
|
||||
// hash is the calculated hash for the current file or directory. hash
|
||||
// is protected by hashMu.
|
||||
hashMu sync.RWMutex `state:"nosave"`
|
||||
hash []byte
|
||||
}
|
||||
|
||||
// newDentry creates a new dentry representing the given verity file. The
|
||||
@@ -519,7 +538,9 @@ func (d *dentry) checkDropLocked(ctx context.Context) {
|
||||
|
||||
// destroyLocked destroys the dentry.
|
||||
//
|
||||
// Preconditions: d.fs.renameMu must be locked for writing. d.refs == 0.
|
||||
// Preconditions:
|
||||
// * d.fs.renameMu must be locked for writing.
|
||||
// * d.refs == 0.
|
||||
func (d *dentry) destroyLocked(ctx context.Context) {
|
||||
switch atomic.LoadInt64(&d.refs) {
|
||||
case 0:
|
||||
@@ -599,6 +620,8 @@ func (d *dentry) checkPermissions(creds *auth.Credentials, ats vfs.AccessTypes)
|
||||
// mode, it returns true if the target has been enabled with
|
||||
// ioctl(FS_IOC_ENABLE_VERITY).
|
||||
func (d *dentry) verityEnabled() bool {
|
||||
d.hashMu.RLock()
|
||||
defer d.hashMu.RUnlock()
|
||||
return !d.fs.allowRuntimeEnable || len(d.hash) != 0
|
||||
}
|
||||
|
||||
@@ -678,11 +701,13 @@ func (fd *fileDescription) Stat(ctx context.Context, opts vfs.StatOptions) (linu
|
||||
if err != nil {
|
||||
return linux.Statx{}, err
|
||||
}
|
||||
fd.d.dirMu.Lock()
|
||||
if fd.d.verityEnabled() {
|
||||
if err := fd.d.fs.verifyStatAndChildren(ctx, fd.d, stat); err != nil {
|
||||
if err := fd.d.fs.verifyStatAndChildrenLocked(ctx, fd.d, stat); err != nil {
|
||||
return linux.Statx{}, err
|
||||
}
|
||||
}
|
||||
fd.d.dirMu.Unlock()
|
||||
return stat, nil
|
||||
}
|
||||
|
||||
@@ -718,13 +743,15 @@ func (fd *fileDescription) Seek(ctx context.Context, offset int64, whence int32)
|
||||
return offset, nil
|
||||
}
|
||||
|
||||
// generateMerkle generates a Merkle tree file for fd. If fd points to a file
|
||||
// /foo/bar, a Merkle tree file /foo/.merkle.verity.bar is generated. The hash
|
||||
// of the generated Merkle tree and the data size is returned. If fd points to
|
||||
// a regular file, the data is the content of the file. If fd points to a
|
||||
// directory, the data is all hahes of its children, written to the Merkle tree
|
||||
// file.
|
||||
func (fd *fileDescription) generateMerkle(ctx context.Context) ([]byte, uint64, error) {
|
||||
// generateMerkleLocked generates a Merkle tree file for fd. If fd points to a
|
||||
// file /foo/bar, a Merkle tree file /foo/.merkle.verity.bar is generated. The
|
||||
// hash of the generated Merkle tree and the data size is returned. If fd
|
||||
// points to a regular file, the data is the content of the file. If fd points
|
||||
// to a directory, the data is all hahes of its children, written to the Merkle
|
||||
// tree file.
|
||||
//
|
||||
// Preconditions: fd.d.fs.verityMu must be locked.
|
||||
func (fd *fileDescription) generateMerkleLocked(ctx context.Context) ([]byte, uint64, error) {
|
||||
fdReader := vfs.FileReadWriteSeeker{
|
||||
FD: fd.lowerFD,
|
||||
Ctx: ctx,
|
||||
@@ -793,11 +820,14 @@ func (fd *fileDescription) generateMerkle(ctx context.Context) ([]byte, uint64,
|
||||
return hash, uint64(params.Size), err
|
||||
}
|
||||
|
||||
// recordChildren writes the names of fd's children into the corresponding
|
||||
// Merkle tree file, and saves the offset/size of the map into xattrs.
|
||||
// recordChildrenLocked writes the names of fd's children into the
|
||||
// corresponding Merkle tree file, and saves the offset/size of the map into
|
||||
// xattrs.
|
||||
//
|
||||
// Preconditions: fd.d.isDir() == true
|
||||
func (fd *fileDescription) recordChildren(ctx context.Context) error {
|
||||
// Preconditions:
|
||||
// * fd.d.fs.verityMu must be locked.
|
||||
// * fd.d.isDir() == true.
|
||||
func (fd *fileDescription) recordChildrenLocked(ctx context.Context) error {
|
||||
// Record the children names in the Merkle tree file.
|
||||
childrenNames, err := json.Marshal(fd.d.childrenNames)
|
||||
if err != nil {
|
||||
@@ -847,7 +877,7 @@ func (fd *fileDescription) enableVerity(ctx context.Context) (uintptr, error) {
|
||||
return 0, alertIntegrityViolation("Unexpected verity fd: missing expected underlying fds")
|
||||
}
|
||||
|
||||
hash, dataSize, err := fd.generateMerkle(ctx)
|
||||
hash, dataSize, err := fd.generateMerkleLocked(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -888,11 +918,13 @@ func (fd *fileDescription) enableVerity(ctx context.Context) (uintptr, error) {
|
||||
}
|
||||
|
||||
if fd.d.isDir() {
|
||||
if err := fd.recordChildren(ctx); err != nil {
|
||||
if err := fd.recordChildrenLocked(ctx); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
fd.d.hash = append(fd.d.hash, hash...)
|
||||
fd.d.hashMu.Lock()
|
||||
fd.d.hash = hash
|
||||
fd.d.hashMu.Unlock()
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
@@ -904,6 +936,9 @@ func (fd *fileDescription) measureVerity(ctx context.Context, verityDigest userm
|
||||
}
|
||||
var metadata linux.DigestMetadata
|
||||
|
||||
fd.d.hashMu.RLock()
|
||||
defer fd.d.hashMu.RUnlock()
|
||||
|
||||
// If allowRuntimeEnable is true, an empty fd.d.hash indicates that
|
||||
// verity is not enabled for the file. If allowRuntimeEnable is false,
|
||||
// this is an integrity violation because all files should have verity
|
||||
@@ -940,11 +975,13 @@ func (fd *fileDescription) measureVerity(ctx context.Context, verityDigest userm
|
||||
func (fd *fileDescription) verityFlags(ctx context.Context, flags usermem.Addr) (uintptr, error) {
|
||||
f := int32(0)
|
||||
|
||||
fd.d.hashMu.RLock()
|
||||
// All enabled files should store a hash. This flag is not settable via
|
||||
// FS_IOC_SETFLAGS.
|
||||
if len(fd.d.hash) != 0 {
|
||||
f |= linux.FS_VERITY_FL
|
||||
}
|
||||
fd.d.hashMu.RUnlock()
|
||||
|
||||
t := kernel.TaskFromContext(ctx)
|
||||
if t == nil {
|
||||
@@ -1023,6 +1060,7 @@ func (fd *fileDescription) PRead(ctx context.Context, dst usermem.IOSequence, of
|
||||
Ctx: ctx,
|
||||
}
|
||||
|
||||
fd.d.hashMu.RLock()
|
||||
n, err := merkletree.Verify(&merkletree.VerifyParams{
|
||||
Out: dst.Writer(ctx),
|
||||
File: &dataReader,
|
||||
@@ -1040,6 +1078,7 @@ func (fd *fileDescription) PRead(ctx context.Context, dst usermem.IOSequence, of
|
||||
Expected: fd.d.hash,
|
||||
DataAndTreeInSameFile: false,
|
||||
})
|
||||
fd.d.hashMu.RUnlock()
|
||||
if err != nil {
|
||||
return 0, alertIntegrityViolation(fmt.Sprintf("Verification failed: %v", err))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user