Add file header (on-disk-format change)
Format: [ "Version" uint16 big endian ] [ "Id" 16 random bytes ] Quoting SECURITY.md: * Every file has a header that contains a 16-byte random *file id* * Each block uses the file id and its block number as GCM *authentication data* * This means the position of the blocks is protected as well. The blocks can not be reordered or copied between different files without causing an decryption error.
This commit is contained in:
parent
73fa8efdb2
commit
76311b60f2
16
README.md
16
README.md
@ -62,3 +62,19 @@ The output should look like this:
|
||||
BenchmarkStreamRead 200 7848155 ns/op 133.61 MB/s
|
||||
ok github.com/rfjakob/gocryptfs 9.407s
|
||||
|
||||
Changelog
|
||||
---------
|
||||
|
||||
v0.3 (in progress)
|
||||
* Add file header that contains a random id to authenticate blocks
|
||||
* This is an on-disk-format change
|
||||
|
||||
v0.2
|
||||
* Replace bash daemonization wrapper with native Go implementation
|
||||
* Better user feedback on mount failures
|
||||
|
||||
v0.1
|
||||
* First release
|
||||
|
||||
See https://github.com/rfjakob/gocryptfs/releases for the release dates
|
||||
and associated tags.
|
||||
|
@ -52,12 +52,11 @@ unless you have the key. The opposite of integrity is *malleability*.
|
||||
* This means that any modification inside a block will be detected when reading
|
||||
the block and decryption will be aborted. The failure is logged and an
|
||||
I/O error is returned to the user.
|
||||
* Each block uses its block number as GCM *authentication data*
|
||||
* Every file has a header that contains a 16-byte random *file id*
|
||||
* Each block uses the file id and its block number as GCM *authentication data*
|
||||
* This means the position of the blocks is protected as well. The blocks
|
||||
can not be reordered without causing an decryption error.
|
||||
* However, proper affiliation of a block to the file is can not be verified.
|
||||
* This means that blocks can be copied between different files provided
|
||||
that they stay at the same position.
|
||||
can not be reordered or copied between different files without
|
||||
causing an decryption error.
|
||||
* For technical reasons (sparse files), the special "all-zero" block is
|
||||
always seen as a valid block that decrypts to all-zero plaintext.
|
||||
* This means that whole blocks can be zeroed out
|
||||
|
@ -1,6 +1,7 @@
|
||||
package cryptfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
)
|
||||
@ -19,6 +20,8 @@ type ConfFile struct {
|
||||
EncryptedKey []byte
|
||||
// Stores parameters for scrypt hashing (key derivation)
|
||||
ScryptObject scryptKdf
|
||||
// The On-Disk-Format version this filesystem uses
|
||||
Version uint16
|
||||
}
|
||||
|
||||
// CreateConfFile - create a new config with a random key encrypted with
|
||||
@ -31,8 +34,11 @@ func CreateConfFile(filename string, password string) error {
|
||||
key := RandBytes(KEY_LEN)
|
||||
|
||||
// Encrypt it using the password
|
||||
// This sets ScryptObject and EncryptedKey
|
||||
cf.EncryptKey(key, password)
|
||||
|
||||
cf.Version = HEADER_CURRENT_VERSION
|
||||
|
||||
// Write file to disk
|
||||
err := cf.WriteFile()
|
||||
|
||||
@ -58,6 +64,10 @@ func LoadConfFile(filename string, password string) ([]byte, *ConfFile, error) {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if cf.Version != HEADER_CURRENT_VERSION {
|
||||
return nil, nil, fmt.Errorf("Unsupported version %d", cf.Version)
|
||||
}
|
||||
|
||||
// Generate derived key from password
|
||||
scryptHash := cf.ScryptObject.DeriveKey(password)
|
||||
|
||||
@ -65,7 +75,7 @@ func LoadConfFile(filename string, password string) ([]byte, *ConfFile, error) {
|
||||
// We use stock go GCM instead of OpenSSL here as speed is not important
|
||||
// and we get better error messages
|
||||
cfs := NewCryptFS(scryptHash, false)
|
||||
key, err := cfs.DecryptBlock(cf.EncryptedKey, 0)
|
||||
key, err := cfs.DecryptBlock(cf.EncryptedKey, 0, nil)
|
||||
if err != nil {
|
||||
Warn.Printf("failed to unlock master key: %s\n", err.Error())
|
||||
Warn.Printf("Password incorrect.\n")
|
||||
@ -84,7 +94,7 @@ func (cf *ConfFile) EncryptKey(key []byte, password string) {
|
||||
|
||||
// Lock master key using password-based key
|
||||
cfs := NewCryptFS(scryptHash, false)
|
||||
cf.EncryptedKey = cfs.EncryptBlock(key, 0)
|
||||
cf.EncryptedKey = cfs.EncryptBlock(key, 0, nil)
|
||||
}
|
||||
|
||||
// WriteFile - write out config in JSON format to file "filename.tmp"
|
||||
|
@ -1,7 +1,6 @@
|
||||
package cryptfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@ -17,6 +16,8 @@ func TestSplitRange(t *testing.T) {
|
||||
testRange{0, 10},
|
||||
testRange{234, 6511},
|
||||
testRange{65444, 54},
|
||||
testRange{0, 1024*1024},
|
||||
testRange{0, 65536},
|
||||
testRange{6654, 8945})
|
||||
|
||||
key := make([]byte, KEY_LEN)
|
||||
@ -24,10 +25,14 @@ func TestSplitRange(t *testing.T) {
|
||||
|
||||
for _, r := range ranges {
|
||||
parts := f.SplitRange(r.offset, r.length)
|
||||
var lastBlockNo uint64 = 1<<63
|
||||
for _, p := range parts {
|
||||
if p.BlockNo == lastBlockNo {
|
||||
t.Errorf("Duplicate block number %d", p.BlockNo)
|
||||
}
|
||||
lastBlockNo = p.BlockNo
|
||||
if p.Length > DEFAULT_PLAINBS || p.Skip >= DEFAULT_PLAINBS {
|
||||
fmt.Printf("Test fail: n=%d, length=%d, offset=%d\n", p.BlockNo, p.Length, p.Skip)
|
||||
t.Fail()
|
||||
t.Errorf("Test fail: n=%d, length=%d, offset=%d\n", p.BlockNo, p.Length, p.Skip)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -48,13 +53,13 @@ func TestCiphertextRange(t *testing.T) {
|
||||
for _, r := range ranges {
|
||||
alignedOffset, alignedLength, skipBytes := f.CiphertextRange(r.offset, r.length)
|
||||
if alignedLength < r.length {
|
||||
t.Fail()
|
||||
t.Errorf("alignedLength=%s is smaller than length=%d", alignedLength, r.length)
|
||||
}
|
||||
if alignedOffset%f.cipherBS != 0 {
|
||||
t.Fail()
|
||||
if (alignedOffset - HEADER_LEN)%f.cipherBS != 0 {
|
||||
t.Errorf("alignedOffset=%d is not aligned", alignedOffset)
|
||||
}
|
||||
if r.offset%f.plainBS != 0 && skipBytes == 0 {
|
||||
t.Fail()
|
||||
t.Errorf("skipBytes=0")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -67,7 +72,7 @@ func TestBlockNo(t *testing.T) {
|
||||
if b != 0 {
|
||||
t.Errorf("actual: %d", b)
|
||||
}
|
||||
b = f.BlockNoCipherOff(f.CipherBS())
|
||||
b = f.BlockNoCipherOff(HEADER_LEN + f.CipherBS())
|
||||
if b != 1 {
|
||||
t.Errorf("actual: %d", b)
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ const (
|
||||
KEY_LEN = 32 // AES-256
|
||||
NONCE_LEN = 12
|
||||
AUTH_TAG_LEN = 16
|
||||
BLOCK_OVERHEAD = NONCE_LEN + AUTH_TAG_LEN
|
||||
)
|
||||
|
||||
type CryptFS struct {
|
||||
|
@ -30,14 +30,14 @@ type CryptFile struct {
|
||||
}
|
||||
|
||||
// DecryptBlocks - Decrypt a number of blocks
|
||||
func (be *CryptFS) DecryptBlocks(ciphertext []byte, firstBlockNo uint64) ([]byte, error) {
|
||||
func (be *CryptFS) DecryptBlocks(ciphertext []byte, firstBlockNo uint64, fileId []byte) ([]byte, error) {
|
||||
cBuf := bytes.NewBuffer(ciphertext)
|
||||
var err error
|
||||
var pBuf bytes.Buffer
|
||||
for cBuf.Len() > 0 {
|
||||
cBlock := cBuf.Next(int(be.cipherBS))
|
||||
var pBlock []byte
|
||||
pBlock, err = be.DecryptBlock(cBlock, firstBlockNo)
|
||||
pBlock, err = be.DecryptBlock(cBlock, firstBlockNo, fileId)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
@ -51,7 +51,7 @@ func (be *CryptFS) DecryptBlocks(ciphertext []byte, firstBlockNo uint64) ([]byte
|
||||
//
|
||||
// Corner case: A full-sized block of all-zero ciphertext bytes is translated
|
||||
// to an all-zero plaintext block, i.e. file hole passtrough.
|
||||
func (be *CryptFS) DecryptBlock(ciphertext []byte, blockNo uint64) ([]byte, error) {
|
||||
func (be *CryptFS) DecryptBlock(ciphertext []byte, blockNo uint64, fileId []byte) ([]byte, error) {
|
||||
|
||||
// Empty block?
|
||||
if len(ciphertext) == 0 {
|
||||
@ -77,6 +77,7 @@ func (be *CryptFS) DecryptBlock(ciphertext []byte, blockNo uint64) ([]byte, erro
|
||||
// Decrypt
|
||||
var plaintext []byte
|
||||
aData := make([]byte, 8)
|
||||
aData = append(aData, fileId...)
|
||||
binary.BigEndian.PutUint64(aData, blockNo)
|
||||
plaintext, err := be.gcm.Open(plaintext, nonce, ciphertext, aData)
|
||||
|
||||
@ -89,8 +90,8 @@ func (be *CryptFS) DecryptBlock(ciphertext []byte, blockNo uint64) ([]byte, erro
|
||||
return plaintext, nil
|
||||
}
|
||||
|
||||
// encryptBlock - Encrypt and add MAC using GCM
|
||||
func (be *CryptFS) EncryptBlock(plaintext []byte, blockNo uint64) []byte {
|
||||
// encryptBlock - Encrypt and add IV and MAC
|
||||
func (be *CryptFS) EncryptBlock(plaintext []byte, blockNo uint64, fileId []byte) []byte {
|
||||
|
||||
// Empty block?
|
||||
if len(plaintext) == 0 {
|
||||
@ -103,6 +104,7 @@ func (be *CryptFS) EncryptBlock(plaintext []byte, blockNo uint64) []byte {
|
||||
// Encrypt plaintext and append to nonce
|
||||
aData := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(aData, blockNo)
|
||||
aData = append(aData, fileId...)
|
||||
ciphertext := be.gcm.Seal(nonce, nonce, plaintext, aData)
|
||||
|
||||
return ciphertext
|
||||
@ -118,6 +120,7 @@ func (be *CryptFS) SplitRange(offset uint64, length uint64) []intraBlock {
|
||||
for length > 0 {
|
||||
b.BlockNo = offset / be.plainBS
|
||||
b.Skip = offset % be.plainBS
|
||||
// Minimum of remaining data and remaining space in the block
|
||||
b.Length = be.minu64(length, be.plainBS-b.Skip)
|
||||
parts = append(parts, b)
|
||||
offset += b.Length
|
||||
@ -134,6 +137,9 @@ func (be *CryptFS) PlainSize(size uint64) uint64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Account for header
|
||||
size -= HEADER_LEN
|
||||
|
||||
overhead := be.cipherBS - be.plainBS
|
||||
nBlocks := (size + be.cipherBS - 1) / be.cipherBS
|
||||
if nBlocks*overhead > size {
|
||||
@ -171,7 +177,7 @@ func (be *CryptFS) CiphertextRange(offset uint64, length uint64) (alignedOffset
|
||||
firstBlockNo := offset / be.plainBS
|
||||
lastBlockNo := (offset + length - 1) / be.plainBS
|
||||
|
||||
alignedOffset = firstBlockNo * be.cipherBS
|
||||
alignedOffset = HEADER_LEN + firstBlockNo * be.cipherBS
|
||||
alignedLength = (lastBlockNo - firstBlockNo + 1) * be.cipherBS
|
||||
|
||||
skipBytes = int(skip)
|
||||
@ -232,5 +238,5 @@ func (be *CryptFS) BlockNoPlainOff(plainOffset uint64) uint64 {
|
||||
|
||||
// Get the block number at ciphter-text offset
|
||||
func (be *CryptFS) BlockNoCipherOff(cipherOffset uint64) uint64 {
|
||||
return cipherOffset / be.cipherBS
|
||||
return (cipherOffset - HEADER_LEN) / be.cipherBS
|
||||
}
|
||||
|
56
cryptfs/file_header.go
Normal file
56
cryptfs/file_header.go
Normal file
@ -0,0 +1,56 @@
|
||||
package cryptfs
|
||||
|
||||
// Per-file header
|
||||
//
|
||||
// Format: [ "Version" uint16 big endian ] [ "Id" 16 random bytes ]
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
HEADER_CURRENT_VERSION = 1 // Current on-disk-format version
|
||||
HEADER_VERSION_LEN = 2 // uint16
|
||||
HEADER_ID_LEN = 16 // 128 bit random file id
|
||||
HEADER_LEN = HEADER_VERSION_LEN + HEADER_ID_LEN // Total header length
|
||||
)
|
||||
|
||||
type FileHeader struct {
|
||||
Version uint16
|
||||
Id []byte
|
||||
}
|
||||
|
||||
// Pack - serialize fileHeader object
|
||||
func (h *FileHeader) Pack() []byte {
|
||||
if len(h.Id) != HEADER_ID_LEN || h.Version != HEADER_CURRENT_VERSION {
|
||||
panic("FileHeader object not properly initialized")
|
||||
}
|
||||
buf := make([]byte, HEADER_LEN)
|
||||
binary.BigEndian.PutUint16(buf[0:HEADER_VERSION_LEN], h.Version)
|
||||
copy(buf[HEADER_VERSION_LEN:], h.Id)
|
||||
return buf
|
||||
|
||||
}
|
||||
|
||||
// ParseHeader - parse "buf" into fileHeader object
|
||||
func ParseHeader(buf []byte) (*FileHeader, error) {
|
||||
if len(buf) != HEADER_LEN {
|
||||
return nil, fmt.Errorf("ParseHeader: invalid length: got %d, want %d", len(buf), HEADER_LEN)
|
||||
}
|
||||
var h FileHeader
|
||||
h.Version = binary.BigEndian.Uint16(buf[0:HEADER_VERSION_LEN])
|
||||
if h.Version != HEADER_CURRENT_VERSION {
|
||||
return nil, fmt.Errorf("ParseHeader: invalid version: got %d, want %d", h.Version, HEADER_CURRENT_VERSION)
|
||||
}
|
||||
h.Id = buf[HEADER_VERSION_LEN:]
|
||||
return &h, nil
|
||||
}
|
||||
|
||||
// RandomHeader - create new fileHeader object with random Id
|
||||
func RandomHeader() *FileHeader {
|
||||
var h FileHeader
|
||||
h.Version = HEADER_CURRENT_VERSION
|
||||
h.Id = RandBytes(HEADER_ID_LEN)
|
||||
return &h
|
||||
}
|
@ -19,7 +19,7 @@ func (ib *intraBlock) IsPartial() bool {
|
||||
// CiphertextRange - get byte range in ciphertext file corresponding to BlockNo
|
||||
// (complete block)
|
||||
func (ib *intraBlock) CiphertextRange() (offset uint64, length uint64) {
|
||||
return ib.BlockNo * ib.fs.cipherBS, ib.fs.cipherBS
|
||||
return HEADER_LEN + ib.BlockNo * ib.fs.cipherBS, ib.fs.cipherBS
|
||||
}
|
||||
|
||||
// PlaintextRange - get byte range in plaintext corresponding to BlockNo
|
||||
|
@ -23,7 +23,7 @@ type file struct {
|
||||
// reuse the fd number after it is closed. When open races
|
||||
// with another close, they may lead to confusion as which
|
||||
// file gets written in the end.
|
||||
lock sync.Mutex
|
||||
fdLock sync.Mutex
|
||||
|
||||
// Was the file opened O_WRONLY?
|
||||
writeOnly bool
|
||||
@ -33,6 +33,9 @@ type file struct {
|
||||
|
||||
// Inode number
|
||||
ino uint64
|
||||
|
||||
// File header
|
||||
header *cryptfs.FileHeader
|
||||
}
|
||||
|
||||
func NewFile(fd *os.File, writeOnly bool, cfs *cryptfs.CryptFS) nodefs.File {
|
||||
@ -54,26 +57,83 @@ func (f *file) InnerFile() nodefs.File {
|
||||
func (f *file) SetInode(n *nodefs.Inode) {
|
||||
}
|
||||
|
||||
// Ensure that all modifications to the file contents are serialized and no
|
||||
// reads happen concurrently.
|
||||
//
|
||||
// This prevents several races:
|
||||
// * getFileId vs Truncate
|
||||
// * zeroPad vs Read
|
||||
// * RMW vs Write
|
||||
func (f *file) wlock() {
|
||||
}
|
||||
func (f *file) rlock() {
|
||||
}
|
||||
func (f *file) unlock() {
|
||||
}
|
||||
|
||||
// readHeader - load the file header from disk
|
||||
//
|
||||
// Returns io.EOF if the file is empty
|
||||
func (f *file) readHeader() error {
|
||||
buf := make([]byte, cryptfs.HEADER_LEN)
|
||||
_, err := f.fd.ReadAt(buf, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h, err := cryptfs.ParseHeader(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.header = h
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// createHeader - create a new random header and write it to disk
|
||||
func (f *file) createHeader() error {
|
||||
h := cryptfs.RandomHeader()
|
||||
buf := h.Pack()
|
||||
_, err := f.fd.WriteAt(buf, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.header = h
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *file) String() string {
|
||||
return fmt.Sprintf("cryptFile(%s)", f.fd.Name())
|
||||
}
|
||||
|
||||
// doRead - returns "length" plaintext bytes from plaintext offset "off".
|
||||
// Arguments "length" and "off" do not have to be aligned.
|
||||
// Arguments "length" and "off" do not have to be block-aligned.
|
||||
//
|
||||
// doRead reads the corresponding ciphertext blocks from disk, decryptfs them and
|
||||
// doRead reads the corresponding ciphertext blocks from disk, decrypts them and
|
||||
// returns the requested part of the plaintext.
|
||||
//
|
||||
// Called by Read() and by Write() and Truncate() for RMW
|
||||
// Called by Read() for normal reading,
|
||||
// by Write() and Truncate() for Read-Modify-Write
|
||||
func (f *file) doRead(off uint64, length uint64) ([]byte, fuse.Status) {
|
||||
|
||||
// Read file header
|
||||
if f.header == nil {
|
||||
err := f.readHeader()
|
||||
if err == io.EOF {
|
||||
return nil, fuse.OK
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fuse.ToStatus(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Read the backing ciphertext in one go
|
||||
alignedOffset, alignedLength, skip := f.cfs.CiphertextRange(off, length)
|
||||
cryptfs.Debug.Printf("CiphertextRange(%d, %d) -> %d, %d, %d\n", off, length, alignedOffset, alignedLength, skip)
|
||||
ciphertext := make([]byte, int(alignedLength))
|
||||
f.lock.Lock()
|
||||
f.fdLock.Lock()
|
||||
n, err := f.fd.ReadAt(ciphertext, int64(alignedOffset))
|
||||
f.lock.Unlock()
|
||||
f.fdLock.Unlock()
|
||||
if err != nil && err != io.EOF {
|
||||
cryptfs.Warn.Printf("read: ReadAt: %s\n", err.Error())
|
||||
return nil, fuse.ToStatus(err)
|
||||
@ -81,14 +141,14 @@ func (f *file) doRead(off uint64, length uint64) ([]byte, fuse.Status) {
|
||||
// Truncate ciphertext buffer down to actually read bytes
|
||||
ciphertext = ciphertext[0:n]
|
||||
|
||||
blockNo := alignedOffset / f.cfs.CipherBS()
|
||||
blockNo := (alignedOffset - cryptfs.HEADER_LEN) / f.cfs.CipherBS()
|
||||
cryptfs.Debug.Printf("ReadAt offset=%d bytes (%d blocks), want=%d, got=%d\n", alignedOffset, blockNo, alignedLength, n)
|
||||
|
||||
// Decrypt it
|
||||
plaintext, err := f.cfs.DecryptBlocks(ciphertext, blockNo)
|
||||
plaintext, err := f.cfs.DecryptBlocks(ciphertext, blockNo, f.header.Id)
|
||||
if err != nil {
|
||||
blockNo := (alignedOffset + uint64(len(plaintext))) / f.cfs.PlainBS()
|
||||
cipherOff := blockNo * f.cfs.CipherBS()
|
||||
cipherOff := cryptfs.HEADER_LEN + blockNo * f.cfs.CipherBS()
|
||||
plainOff := blockNo * f.cfs.PlainBS()
|
||||
cryptfs.Warn.Printf("ino%d: doRead: corrupt block #%d (plainOff=%d/%d, cipherOff=%d/%d)\n",
|
||||
f.ino, blockNo, plainOff, f.cfs.PlainBS(), cipherOff, f.cfs.CipherBS())
|
||||
@ -103,15 +163,15 @@ func (f *file) doRead(off uint64, length uint64) ([]byte, fuse.Status) {
|
||||
out = plaintext[skip : skip+int(length)]
|
||||
} else if lenHave > skip {
|
||||
out = plaintext[skip:lenHave]
|
||||
} else {
|
||||
// Out stays empty, file was smaller than the requested offset
|
||||
}
|
||||
// else: out stays empty, file was smaller than the requested offset
|
||||
|
||||
return out, fuse.OK
|
||||
}
|
||||
|
||||
// Read - FUSE call
|
||||
func (f *file) Read(buf []byte, off int64) (resultData fuse.ReadResult, code fuse.Status) {
|
||||
|
||||
cryptfs.Debug.Printf("ino%d: FUSE Read: offset=%d length=%d\n", f.ino, len(buf), off)
|
||||
|
||||
if f.writeOnly {
|
||||
@ -132,8 +192,27 @@ func (f *file) Read(buf []byte, off int64) (resultData fuse.ReadResult, code fus
|
||||
return fuse.ReadResultData(out), status
|
||||
}
|
||||
|
||||
// Do the actual write
|
||||
// doWrite - encrypt "data" and write it to plaintext offset "off"
|
||||
//
|
||||
// Arguments do not have to be block-aligned, read-modify-write is
|
||||
// performed internally as neccessary
|
||||
//
|
||||
// Called by Write() for normal writing,
|
||||
// and by Truncate() to rewrite the last file block.
|
||||
func (f *file) doWrite(data []byte, off int64) (uint32, fuse.Status) {
|
||||
|
||||
// Read header from disk, create a new one if the file is empty
|
||||
if f.header == nil {
|
||||
err := f.readHeader()
|
||||
if err == io.EOF {
|
||||
err = f.createHeader()
|
||||
|
||||
}
|
||||
if err != nil {
|
||||
return 0, fuse.ToStatus(err)
|
||||
}
|
||||
}
|
||||
|
||||
var written uint32
|
||||
status := fuse.OK
|
||||
dataBuf := bytes.NewBuffer(data)
|
||||
@ -158,14 +237,16 @@ func (f *file) doWrite(data []byte, off int64) (uint32, fuse.Status) {
|
||||
|
||||
// Write
|
||||
blockOffset, _ := b.CiphertextRange()
|
||||
blockData = f.cfs.EncryptBlock(blockData, b.BlockNo)
|
||||
cryptfs.Debug.Printf("ino%d: Writing %d bytes to block #%d, md5=%s\n", f.ino, len(blockData), b.BlockNo, cryptfs.Debug.Md5sum(blockData))
|
||||
blockData = f.cfs.EncryptBlock(blockData, b.BlockNo, f.header.Id)
|
||||
cryptfs.Debug.Printf("ino%d: Writing %d bytes to block #%d, md5=%s\n",
|
||||
f.ino, len(blockData) - cryptfs.BLOCK_OVERHEAD, b.BlockNo, cryptfs.Debug.Md5sum(blockData))
|
||||
if len(blockData) != int(f.cfs.CipherBS()) {
|
||||
cryptfs.Debug.Printf("ino%d: Writing partial block #%d (%d bytes)\n", f.ino, b.BlockNo, len(blockData))
|
||||
cryptfs.Debug.Printf("ino%d: Writing partial block #%d (%d bytes)\n",
|
||||
f.ino, b.BlockNo, len(blockData) - cryptfs.BLOCK_OVERHEAD)
|
||||
}
|
||||
f.lock.Lock()
|
||||
f.fdLock.Lock()
|
||||
_, err := f.fd.WriteAt(blockData, int64(blockOffset))
|
||||
f.lock.Unlock()
|
||||
f.fdLock.Unlock()
|
||||
|
||||
if err != nil {
|
||||
cryptfs.Warn.Printf("Write failed: %s\n", err.Error())
|
||||
@ -199,20 +280,20 @@ func (f *file) Write(data []byte, off int64) (uint32, fuse.Status) {
|
||||
|
||||
// Release - FUSE call, forget file
|
||||
func (f *file) Release() {
|
||||
f.lock.Lock()
|
||||
f.fdLock.Lock()
|
||||
f.fd.Close()
|
||||
f.lock.Unlock()
|
||||
f.fdLock.Unlock()
|
||||
}
|
||||
|
||||
// Flush - FUSE call
|
||||
func (f *file) Flush() fuse.Status {
|
||||
f.lock.Lock()
|
||||
f.fdLock.Lock()
|
||||
|
||||
// Since Flush() may be called for each dup'd fd, we don't
|
||||
// want to really close the file, we just want to flush. This
|
||||
// is achieved by closing a dup'd fd.
|
||||
newFd, err := syscall.Dup(int(f.fd.Fd()))
|
||||
f.lock.Unlock()
|
||||
f.fdLock.Unlock()
|
||||
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
@ -222,14 +303,27 @@ func (f *file) Flush() fuse.Status {
|
||||
}
|
||||
|
||||
func (f *file) Fsync(flags int) (code fuse.Status) {
|
||||
f.lock.Lock()
|
||||
f.fdLock.Lock()
|
||||
r := fuse.ToStatus(syscall.Fsync(int(f.fd.Fd())))
|
||||
f.lock.Unlock()
|
||||
f.fdLock.Unlock()
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (f *file) Truncate(newSize uint64) fuse.Status {
|
||||
// Common case first: Truncate to zero
|
||||
if newSize == 0 {
|
||||
f.fdLock.Lock()
|
||||
err := syscall.Ftruncate(int(f.fd.Fd()), 0)
|
||||
f.fdLock.Unlock()
|
||||
if err != nil {
|
||||
cryptfs.Warn.Printf("Ftruncate(fd, 0) returned error: %v", err)
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
// A truncate to zero kills the file header
|
||||
f.header = nil
|
||||
return fuse.OK
|
||||
}
|
||||
|
||||
// We need the old file size to determine if we are growing or shrinking
|
||||
// the file
|
||||
@ -247,6 +341,15 @@ func (f *file) Truncate(newSize uint64) fuse.Status {
|
||||
|
||||
// File grows
|
||||
if newSize > oldSize {
|
||||
|
||||
// File was empty, create new header
|
||||
if oldSize == 0 {
|
||||
err := f.createHeader()
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
}
|
||||
|
||||
blocks := f.cfs.SplitRange(oldSize, newSize-oldSize)
|
||||
for _, b := range blocks {
|
||||
// First and last block may be partial
|
||||
@ -259,9 +362,9 @@ func (f *file) Truncate(newSize uint64) fuse.Status {
|
||||
}
|
||||
} else {
|
||||
off, length := b.CiphertextRange()
|
||||
f.lock.Lock()
|
||||
f.fdLock.Lock()
|
||||
err := syscall.Ftruncate(int(f.fd.Fd()), int64(off+length))
|
||||
f.lock.Unlock()
|
||||
f.fdLock.Unlock()
|
||||
if err != nil {
|
||||
cryptfs.Warn.Printf("grow Ftruncate returned error: %v", err)
|
||||
return fuse.ToStatus(err)
|
||||
@ -272,7 +375,7 @@ func (f *file) Truncate(newSize uint64) fuse.Status {
|
||||
} else {
|
||||
// File shrinks
|
||||
blockNo := f.cfs.BlockNoPlainOff(newSize)
|
||||
cipherOff := blockNo * f.cfs.CipherBS()
|
||||
cipherOff := cryptfs.HEADER_LEN + blockNo * f.cfs.CipherBS()
|
||||
plainOff := blockNo * f.cfs.PlainBS()
|
||||
lastBlockLen := newSize - plainOff
|
||||
var data []byte
|
||||
@ -284,13 +387,15 @@ func (f *file) Truncate(newSize uint64) fuse.Status {
|
||||
return status
|
||||
}
|
||||
}
|
||||
f.lock.Lock()
|
||||
// Truncate down to last complete block
|
||||
f.fdLock.Lock()
|
||||
err = syscall.Ftruncate(int(f.fd.Fd()), int64(cipherOff))
|
||||
f.lock.Unlock()
|
||||
f.fdLock.Unlock()
|
||||
if err != nil {
|
||||
cryptfs.Warn.Printf("shrink Ftruncate returned error: %v", err)
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
// Append partial block
|
||||
if lastBlockLen > 0 {
|
||||
_, status := f.doWrite(data, int64(plainOff))
|
||||
return status
|
||||
@ -300,17 +405,17 @@ func (f *file) Truncate(newSize uint64) fuse.Status {
|
||||
}
|
||||
|
||||
func (f *file) Chmod(mode uint32) fuse.Status {
|
||||
f.lock.Lock()
|
||||
f.fdLock.Lock()
|
||||
r := fuse.ToStatus(f.fd.Chmod(os.FileMode(mode)))
|
||||
f.lock.Unlock()
|
||||
f.fdLock.Unlock()
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (f *file) Chown(uid uint32, gid uint32) fuse.Status {
|
||||
f.lock.Lock()
|
||||
f.fdLock.Lock()
|
||||
r := fuse.ToStatus(f.fd.Chown(int(uid), int(gid)))
|
||||
f.lock.Unlock()
|
||||
f.fdLock.Unlock()
|
||||
|
||||
return r
|
||||
}
|
||||
@ -318,9 +423,9 @@ func (f *file) Chown(uid uint32, gid uint32) fuse.Status {
|
||||
func (f *file) GetAttr(a *fuse.Attr) fuse.Status {
|
||||
cryptfs.Debug.Printf("file.GetAttr()\n")
|
||||
st := syscall.Stat_t{}
|
||||
f.lock.Lock()
|
||||
f.fdLock.Lock()
|
||||
err := syscall.Fstat(int(f.fd.Fd()), &st)
|
||||
f.lock.Unlock()
|
||||
f.fdLock.Unlock()
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
@ -330,7 +435,7 @@ func (f *file) GetAttr(a *fuse.Attr) fuse.Status {
|
||||
return fuse.OK
|
||||
}
|
||||
|
||||
// Allocate FUSE call, fallocate(2)
|
||||
// Allocate - FUSE call, fallocate(2)
|
||||
func (f *file) Allocate(off uint64, sz uint64, mode uint32) fuse.Status {
|
||||
cryptfs.Warn.Printf("Fallocate is not supported, returning ENOSYS - see https://github.com/rfjakob/gocryptfs/issues/1\n")
|
||||
return fuse.ENOSYS
|
||||
@ -354,9 +459,9 @@ func (f *file) Utimens(a *time.Time, m *time.Time) fuse.Status {
|
||||
ts[1].Sec = m.Unix()
|
||||
}
|
||||
|
||||
f.lock.Lock()
|
||||
f.fdLock.Lock()
|
||||
fn := fmt.Sprintf("/proc/self/fd/%d", f.fd.Fd())
|
||||
err := syscall.UtimesNano(fn, ts)
|
||||
f.lock.Unlock()
|
||||
f.fdLock.Unlock()
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
|
@ -119,7 +119,8 @@ func (fs *FS) Mknod(name string, mode uint32, dev uint32, context *fuse.Context)
|
||||
}
|
||||
|
||||
func (fs *FS) Truncate(path string, offset uint64, context *fuse.Context) (code fuse.Status) {
|
||||
return fs.FileSystem.Truncate(fs.EncryptPath(path), offset, context)
|
||||
cryptfs.Warn.Printf("Truncate of a closed file is not supported, returning ENOSYS\n")
|
||||
return fuse.ENOSYS
|
||||
}
|
||||
|
||||
func (fs *FS) Utimens(path string, Atime *time.Time, Mtime *time.Time, context *fuse.Context) (code fuse.Status) {
|
||||
|
Loading…
Reference in New Issue
Block a user