Skip to content

Commit

Permalink
Exclusive keystore locking (#3907)
Browse files Browse the repository at this point in the history
  • Loading branch information
cheatfate authored Aug 7, 2022
1 parent fe5435e commit 250f7b4
Show file tree
Hide file tree
Showing 8 changed files with 606 additions and 237 deletions.
99 changes: 93 additions & 6 deletions beacon_chain/filepath.nim
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,59 @@ else:

import chronicles
import stew/io2
export io2
import spec/keystore

when defined(windows):
import stew/[windows/acl]

type
ByteChar = byte | char

const
INCOMPLETE_ERROR =
when defined(windows):
IoErrorCode(996) # ERROR_IO_INCOMPLETE
else:
IoErrorCode(28) # ENOSPC

proc openLockedFile*(keystorePath: string): IoResult[FileLockHandle] =
let
flags = {OpenFlags.Read, OpenFlags.Write, OpenFlags.Exclusive}
handle = ? openFile(keystorePath, flags)

var success = false
defer:
if not(success):
discard closeFile(handle)

let lock = ? lockFile(handle, LockType.Exclusive)
success = true
ok(FileLockHandle(ioHandle: lock, opened: true))

proc getData*(lockHandle: FileLockHandle,
maxBufferSize: int): IoResult[string] =
let filesize = ? getFileSize(lockHandle.ioHandle.handle)
let length = min(filesize, maxBufferSize)
var buffer = newString(length)
let bytesRead = ? readFile(lockHandle.ioHandle.handle, buffer)
if uint64(bytesRead) != uint64(len(buffer)):
err(INCOMPLETE_ERROR)
else:
ok(buffer)

proc closeLockedFile*(lockHandle: FileLockHandle): IoResult[void] =
if lockHandle.opened:
var success = false
defer:
lockHandle.opened = false
if not(success):
discard lockHandle.ioHandle.handle.closeFile()

? lockHandle.ioHandle.unlockFile()
success = true
? lockHandle.ioHandle.handle.closeFile()
ok()

proc secureCreatePath*(path: string): IoResult[void] =
when defined(windows):
let sres = createFoldersUserOnlySecurityDescriptor()
Expand All @@ -31,16 +79,55 @@ proc secureCreatePath*(path: string): IoResult[void] =
else:
createPath(path, 0o700)

proc secureWriteFile*[T: byte|char](path: string,
data: openArray[T]): IoResult[void] =
proc secureWriteFile*[T: ByteChar](path: string,
data: openArray[T]): IoResult[void] =
when defined(windows):
let sres = createFilesUserOnlySecurityDescriptor()
if sres.isErr():
error "Could not allocate security descriptor", path = path,
errorMsg = ioErrorMsg(sres.error), errorCode = $sres.error
err(sres.error)
err(sres.error())
else:
var sd = sres.get()
writeFile(path, data, 0o600, secDescriptor = sd.getDescriptor())
let res = writeFile(path, data, 0o600, sd.getDescriptor())
if res.isErr():
# writeFile() will not attempt to remove file on failure
discard removeFile(path)
err(res.error())
else:
ok()
else:
writeFile(path, data, 0o600)
let res = writeFile(path, data, 0o600)
if res.isErr():
# writeFile() will not attempt to remove file on failure
discard removeFile(path)
err(res.error())
else:
ok()

proc secureWriteLockedFile*[T: ByteChar](path: string,
data: openArray[T]
): IoResult[FileLockHandle] =
let handle =
block:
let flags = {OpenFlags.Write, OpenFlags.Truncate, OpenFlags.Create,
OpenFlags.Exclusive}
when defined(windows):
var sd = ? createFilesUserOnlySecurityDescriptor()
? openFile(path, flags, 0o600, sd.getDescriptor())
else:
? openFile(path, flags, 0o600)
var success = false
defer:
if not(success):
discard closeFile(handle)
# We will try to remove file, if something goes wrong.
discard removeFile(path)
let bytesWrote = ? writeFile(handle, data)
if uint64(bytesWrote) != uint64(len(data)):
# Data was partially written, and `write` did not return any errors, so
# lets return INCOMPLETE_ERROR.
return err(INCOMPLETE_ERROR)
let res = ? lockFile(handle, LockType.Exclusive)
success = true
ok(FileLockHandle(ioHandle: res, opened: true))
1 change: 1 addition & 0 deletions beacon_chain/nimbus_beacon_node.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1562,6 +1562,7 @@ proc stop(node: BeaconNode) =
warn "Couldn't stop network", msg = exc.msg

node.attachedValidators.slashingProtection.close()
node.attachedValidators[].close()
node.db.close()
notice "Databases closed"

Expand Down
9 changes: 7 additions & 2 deletions beacon_chain/spec/keystore.nim
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ import
# Third-party libraries
normalize,
# Status libraries
stew/[results, bitops2, base10], stew/shims/macros,
stew/[results, bitops2, base10, io2], stew/shims/macros,
eth/keyfile/uuid, blscurve, json_serialization,
nimcrypto/[sha2, rijndael, pbkdf2, bcmode, hash, scrypt],
# Local modules
libp2p/crypto/crypto as lcrypto,
./datatypes/base, ./signatures

export base, uri
export base, uri, io2

# We use `ncrutils` for constant-time hexadecimal encoding/decoding procedures.
import nimcrypto/utils as ncrutils
Expand Down Expand Up @@ -140,10 +140,15 @@ type
id*: uint32
pubkey*: ValidatorPubKey

FileLockHandle* = ref object
ioHandle*: IoLockHandle
opened*: bool

KeystoreData* = object
version*: uint64
pubkey*: ValidatorPubKey
description*: Option[string]
handle*: FileLockHandle
case kind*: KeystoreKind
of KeystoreKind.Local:
privateKey*: ValidatorPrivKey
Expand Down
Loading

0 comments on commit 250f7b4

Please sign in to comment.