memguard

package module
v0.7.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 5, 2017 License: Apache-2.0 Imports: 10 Imported by: 0

README

MemGuard

A pure Go library that handles sensitive values in memory.


This is a thread-safe package, designed to allow you to easily handle sensitive values in memory. It supports all major operating systems and is written in pure Go.

Features

  • Memory is allocated using system calls, thereby bypassing the Go runtime and preventing the GC from messing with it.
  • To prevent buffer overflows and underflows, the secure buffer is sandwiched between two protected guard pages. If these pages are accessed, a SIGSEGV violation is triggered.
  • The secure buffer is prepended with a random canary. If this value changes, the process will panic. This is designed to prevent buffer underflows.
  • All pages between the two guards are locked to stop them from being swapped to disk.
  • The secure buffer can be made read-only so that any other action triggers a SIGSEGV violation.
  • When freeing, all secure memory is wiped.
  • The API also includes functions for time-constant copying and comparison, disabling system core dumps, and catching signals.

Some of these features were inspired by libsodium, so credits to them.

Full documentation and a complete overview of the API can be found here.

Installation

Although we do recommend using a release, the simplest way to install the library is to go get it:

$ go get github.com/0xAwn/memguard

If you would prefer a signed release that you can verify and manually compile yourself, download and extract the latest release. Then go ahead and run:

$ go install -v ./

The releases are cryptographically signed with my PGP key, which can be found on keybase. To import it directly into GPG, run:

$ curl https://keybase.io/awn/pgp_keys.asc | gpg --import

Documentation

Overview

Package memguard lets you easily handle sensitive values in memory.

The general working cycle is as follows:

// Create a new writable LockedBuffer of length 16.
encryptionKey, err := memguard.New(16, false)
if err != nil {
    panic(err)
}
defer encryptionKey.Destroy()

// Move bytes into the buffer.
// Do not append or assign, use API call.
encryptionKey.Move([]byte("yellow submarine"))

// Use the buffer wherever you need it.
Encrypt(encryptionKey.Buffer, plaintext)

The number of LockedBuffers that you are able to create is limited by how much memory your system kernel allows each process to mlock/VirtualLock. Therefore you should call Destroy on LockedBuffers that you no longer need, or simply defer a Destroy call after creating a new LockedBuffer.

If a function that you're using requires an array, you can cast the Buffer to an array and then pass around a pointer. Make sure that you do not dereference the pointer and pass around the resulting value, as this will leave copies all over the place.

key, err := memguard.NewRandom(16, false)
if err != nil {
    panic(err)
}
defer key.Destroy()

// Make sure the size of the array matches the size of the Buffer.
// In this case that size is 16. This is very important.
keyArrayPtr := (*[16]byte)(unsafe.Pointer(&key.Buffer[0]))

The MemGuard API is thread-safe. You can extend this thread-safety to outside of the API functions by using the Mutex that each LockedBuffer exposes. Don't use the mutex when calling a function that is part of the MemGuard API though, or the process will deadlock.

When terminating your application, care should be taken to securely cleanup everything.

// Start a listener that will wait for interrupt signals and catch them.
memguard.CatchInterrupt(func() {
    // Over here put anything you want executing before program exit.
    fmt.Println("Interrupt signal received. Exiting...")
})

// Defer a DestroyAll() in your main() function.
defer memguard.DestroyAll()

// Use memguard.SafeExit() instead of os.Exit().
memguard.SafeExit(0) // 0 is the status code.

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrDestroyed = errors.New("memguard.ErrDestroyed: buffer is destroyed")

ErrDestroyed is returned when a function is called on a destroyed LockedBuffer.

View Source
var ErrInvalidLength = errors.New("memguard.ErrInvalidLength: length of buffer must be greater than zero")

ErrInvalidLength is returned when a LockedBuffer of smaller than one byte is requested.

View Source
var ErrReadOnly = errors.New("memguard.ErrReadOnly: buffer is marked read-only")

ErrReadOnly is returned when a function that needs to modify a LockedBuffer is given a LockedBuffer that is marked as being read-only.

Functions

func CatchInterrupt

func CatchInterrupt(f func())

CatchInterrupt starts a goroutine that monitors for interrupt signals. It accepts a function of type func() and executes that before calling SafeExit(0).

If CatchInterrupt is called multiple times, only the first call is executed and all subsequent calls are ignored.

Example
CatchInterrupt(func() {
	fmt.Println("Exiting...")
})
Output:

func DestroyAll

func DestroyAll()

DestroyAll calls Destroy on all LockedBuffers that have not already been destroyed.

CatchInterrupt and SafeExit both call DestroyAll before exiting.

func DisableUnixCoreDumps added in v0.5.0

func DisableUnixCoreDumps()

DisableUnixCoreDumps disables core-dumps.

Since core-dumps are only relevant on Unix systems, if DisableUnixCoreDumps is called on any other system it will do nothing and return immediately.

This function is precautonary as core-dumps are usually disabled by default on most systems.

func Equal added in v0.4.0

func Equal(a, b *LockedBuffer) (bool, error)

Equal compares the contents of two LockedBuffers in constant time.

func SafeExit

func SafeExit(c int)

SafeExit exits the program with a specified exit-code, but calls DestroyAll first.

func Split added in v0.4.0

func Split(b *LockedBuffer, offset int) (*LockedBuffer, *LockedBuffer, error)

Split takes a LockedBuffer, splits it at a specified offset, and then returns the two newly created LockedBuffers. The permissions of the original are preserved and the original LockedBuffer is not destroyed.

func WipeBytes

func WipeBytes(buf []byte)

WipeBytes wipes a byte slice with zeroes.

Types

type LockedBuffer

type LockedBuffer struct {
	sync.Mutex
	Buffer    []byte
	ReadOnly  bool
	Destroyed bool
}

LockedBuffer is a structure that holds secure values. It exposes a Mutex, various metadata flags, and a slice that maps to the protected memory.

The number of LockedBuffers that you are able to create is limited by how much memory your system kernel allows each process to mlock/VirtualLock. Therefore you should call Destroy on LockedBuffers that you no longer need, or simply defer a Destroy call after creating a new LockedBuffer.

The entire memguard API handles and passes around pointers to LockedBuffers, and so, for both security and convenience, you should refrain from dereferencing a LockedBuffer.

If an API function that needs to edit a LockedBuffer is given one marked as read-only, the call will return an ErrReadOnly. Similarly, if a function is given a LockedBuffer that has been destroyed, the call will return an ErrDestroyed.

For obvious reasons, you should never edit these metadata values yourself. Doing so will result in undefined behaviour.

func Concatenate added in v0.7.0

func Concatenate(a, b *LockedBuffer) (*LockedBuffer, error)

Concatenate takes two LockedBuffers and concatenates them.

If one of the given LockedBuffers is read-only, the resulting LockedBuffer will also be read-only. The original LockedBuffers are not destroyed.

func Duplicate added in v0.4.0

func Duplicate(b *LockedBuffer) (*LockedBuffer, error)

Duplicate takes a LockedBuffer and creates a new one with the same contents and permissions as the original.

func LockedBuffers added in v0.5.0

func LockedBuffers() []*LockedBuffer

LockedBuffers returns a slice containing a pointer to each LockedBuffer that has not been destroyed.

func New

func New(length int, readOnly bool) (*LockedBuffer, error)

New creates a new LockedBuffer of a specified length and permissions.

If the given length is less than one, the call will return an ErrInvalidLength.

Example
key, err := New(32, false)
if err != nil {
	fmt.Println(err)
	SafeExit(1)
}
defer key.Destroy()
Output:

func NewFromBytes

func NewFromBytes(buf []byte, readOnly bool) (*LockedBuffer, error)

NewFromBytes is identical to New but for the fact that the created LockedBuffer is of the same length and has the same contents as a given slice. The slice is wiped after the bytes have been copied over.

If the size of the slice is zero, the call will return an ErrInvalidLength.

func NewRandom added in v0.5.0

func NewRandom(length int, readOnly bool) (*LockedBuffer, error)

NewRandom is identical to New but for the fact that the created LockedBuffer is filled with cryptographically-secure pseudo-random bytes instead of zeroes. Therefore a LockedBuffer created with NewRandom can safely be used as an encryption key.

func Trim added in v0.4.0

func Trim(b *LockedBuffer, offset, size int) (*LockedBuffer, error)

Trim shortens a LockedBuffer according to the given specifications. The permissions of the original are preserved and the original LockedBuffer is not destroyed.

Trim takes an offset and a size as arguments. The resulting LockedBuffer starts at index [offset] and ends at index [offset+size].

func (*LockedBuffer) Copy

func (b *LockedBuffer) Copy(buf []byte) error

Copy copies bytes from a byte slice into a LockedBuffer in constant-time. Just like Golang's built-in copy function, Copy only copies up to the smallest of the two buffers.

It does not wipe the original slice so using Copy is less secure than using Move. Therefore Move should be favoured unless you have a good reason.

You should aim to call WipeBytes on the original slice as soon as possible.

If the LockedBuffer is marked as read-only, the call will fail and return an ErrReadOnly.

func (*LockedBuffer) CopyAt added in v0.4.0

func (b *LockedBuffer) CopyAt(buf []byte, offset int) error

CopyAt is identical to Copy but it copies into the LockedBuffer at a specified offset.

func (*LockedBuffer) Destroy

func (b *LockedBuffer) Destroy()

Destroy verifies that no buffer underflows occurred and then wipes, unlocks, and frees all related memory. If a buffer underflow is detected, the process panics.

This function must be called on all LockedBuffers before exiting. DestroyAll is designed for this purpose, as is CatchInterrupt and SafeExit. We recommend using all of them together.

If the LockedBuffer has already been destroyed then the call makes no changes.

func (*LockedBuffer) EqualTo added in v0.4.0

func (b *LockedBuffer) EqualTo(buf []byte) (bool, error)

EqualTo compares a LockedBuffer to a byte slice in constant time.

func (*LockedBuffer) FillRandomBytes added in v0.6.0

func (b *LockedBuffer) FillRandomBytes() error

FillRandomBytes fills a LockedBuffer with cryptographically-secure pseudo-random bytes.

func (*LockedBuffer) FillRandomBytesAt added in v0.6.0

func (b *LockedBuffer) FillRandomBytesAt(offset, length int) error

FillRandomBytesAt fills a LockedBuffer with cryptographically-secure pseudo-random bytes, starting at an offset and ending after a given number of bytes.

func (*LockedBuffer) MarkAsReadOnly added in v0.4.0

func (b *LockedBuffer) MarkAsReadOnly() error

MarkAsReadOnly asks the kernel to mark the LockedBuffer's memory as read-only. Any subsequent attempts to write to this memory will result in the process crashing with a SIGSEGV memory violation.

To make the memory writable again, MarkAsReadWrite is called.

func (*LockedBuffer) MarkAsReadWrite added in v0.4.0

func (b *LockedBuffer) MarkAsReadWrite() error

MarkAsReadWrite asks the kernel to mark the LockedBuffer's memory as readable and writable.

This method is the counterpart of MarkAsReadOnly.

func (*LockedBuffer) Move

func (b *LockedBuffer) Move(buf []byte) error

Move moves bytes from a byte slice into a LockedBuffer in constant-time. Just like Golang's built-in copy function, Move only moves up to the smallest of the two buffers.

Unlike Copy, Move wipes the entire original slice after copying the appropriate number of bytes over, and so it should be favoured unless you have a good reason.

If the LockedBuffer is marked as read-only, the call will fail and return an ErrReadOnly.

func (*LockedBuffer) MoveAt added in v0.4.0

func (b *LockedBuffer) MoveAt(buf []byte, offset int) error

MoveAt is identical to Move but it copies into the LockedBuffer at a specified offset.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL