package core import ( "sync" "github.com/awnumar/memguard/memcall" "gitlab.com/NebulousLabs/fastrand" ) var ( buffers = new(bufferList) ) /* Buffer is a structure that holds raw sensitive data. The number of Buffers that can exist at one time is limited by how much memory your system's kernel allows each process to mlock/VirtualLock. Therefore you should call DestroyBuffer on Buffers that you no longer need, ideally defering a Destroy call after creating a new one. */ type Buffer struct { sync.RWMutex // Local mutex lock alive bool // Signals that destruction has not come mutable bool // Mutability state of underlying memory data []byte // Portion of memory holding the data memory []byte // Entire allocated memory region preguard []byte // Guard page addressed before the data inner []byte // Inner region between the guard pages postguard []byte // Guard page addressed after the data canary []byte // Value written behind data to detect spillage } /* NewBuffer is a raw constructor for the Buffer object. */ func NewBuffer(size int) (*Buffer, error) { var err error // Return an error if length < 1. if size < 1 { return nil, errors[errCodeNullBuffer] } // Declare and allocate b := new(Buffer) // Allocate the total needed memory innerLen := roundToPageSize(size) b.memory, err = memcall.Alloc((2 * pageSize) + innerLen) if err != nil { Panic(err) } // Construct slice reference for data buffer. b.data = getBytes(&b.memory[pageSize+innerLen-size], size) // Construct slice references for page sectors. b.preguard = getBytes(&b.memory[0], pageSize) b.inner = getBytes(&b.memory[pageSize], innerLen) b.postguard = getBytes(&b.memory[pageSize+innerLen], pageSize) // Construct slice reference for canary portion of inner page. b.canary = getBytes(&b.memory[pageSize], len(b.inner)-len(b.data)) // Lock the pages that will hold sensitive data. if err := memcall.Lock(b.inner); err != nil { Panic(err) } // Initialise the canary value and reference regions. fastrand.Read(b.canary) Copy(b.preguard, b.canary) Copy(b.postguard, b.canary) // Make the guard pages inaccessible. if err := memcall.Protect(b.preguard, memcall.NoAccess); err != nil { Panic(err) } if err := memcall.Protect(b.postguard, memcall.NoAccess); err != nil { Panic(err) } // Set remaining properties b.alive = true b.mutable = true // Append the container to list of active buffers. buffers.add(b) // Return the created Buffer to the caller. return b, nil } // Data returns a byte slice representing the memory region containing the data. func (b *Buffer) Data() []byte { return b.data } // Freeze makes the underlying memory of a given buffer immutable. This will do nothing if the Buffer has been destroyed. func (b *Buffer) Freeze() { // Attain lock. b.Lock() defer b.Unlock() // Check if destroyed. if !b.alive { return } // Only do anything if currently mutable. if b.mutable { // Make the memory immutable. if err := memcall.Protect(b.inner, memcall.ReadOnly); err != nil { Panic(err) } b.mutable = false } } // Melt makes the underlying memory of a given buffer mutable. This will do nothing if the Buffer has been destroyed. func (b *Buffer) Melt() { // Attain lock. b.Lock() defer b.Unlock() // Check if destroyed. if !b.alive { return } // Only do anything if currently immutable. if !b.mutable { // Make the memory mutable. if err := memcall.Protect(b.inner, memcall.ReadWrite); err != nil { Panic(err) } b.mutable = true } } /* Destroy performs some security checks, securely wipes the contents of, and then releases a Buffer's memory back to the OS. If a security check fails, the process will attempt to wipe all it can before safely panicking. If the Buffer has already been destroyed, subsequent calls are idempotent. */ func (b *Buffer) Destroy() { // Attain a mutex lock on this Buffer. b.Lock() defer b.Unlock() // Return if it's already destroyed. if !b.alive { return } // Make all of the memory readable and writable. if err := memcall.Protect(b.memory, memcall.ReadWrite); err != nil { Panic(err) } // Verify the canary if !Equal(b.preguard, b.postguard) || !Equal(b.preguard[:len(b.canary)], b.canary) { Panic(" canary verification failed; buffer overflow detected") } // Wipe the memory. Wipe(b.memory) // Remove this one from global slice. buffers.remove(b) // Unlock pages locked into memory. if err := memcall.Unlock(b.inner); err != nil { Panic(err) } // Free all related memory. if err := memcall.Free(b.memory); err != nil { Panic(err) } // Reset the fields. b.alive = false b.mutable = false b.data = nil b.memory = nil b.preguard = nil b.inner = nil b.postguard = nil b.canary = nil } // Alive returns true if the buffer has not been destroyed. func (b *Buffer) Alive() bool { b.RLock() defer b.RUnlock() return b.alive } // Mutable returns true if the buffer is mutable. func (b *Buffer) Mutable() bool { b.RLock() defer b.RUnlock() return b.mutable } // BufferList stores a list of buffers in a thread-safe manner. type bufferList struct { sync.RWMutex list []*Buffer } // Add appends a given Buffer to the list. func (l *bufferList) add(b ...*Buffer) { l.Lock() defer l.Unlock() l.list = append(l.list, b...) } // Remove removes a given Buffer from the list. func (l *bufferList) remove(b *Buffer) { l.Lock() defer l.Unlock() for i, v := range l.list { if v == b { l.list = append(l.list[:i], l.list[i+1:]...) break } } } // Exists checks if a given buffer is in the list. func (l *bufferList) exists(b *Buffer) bool { l.RLock() defer l.RUnlock() for _, v := range l.list { if b == v { return true } } return false } // Flush clears the list and returns its previous contents. func (l *bufferList) flush() []*Buffer { l.Lock() defer l.Unlock() list := make([]*Buffer, len(l.list)) copy(list, l.list) l.list = nil return list }