Initial commit

This commit is contained in:
2026-05-31 01:09:34 +01:00
commit 1d51b29b92
31 changed files with 4874 additions and 0 deletions
+114
View File
@@ -0,0 +1,114 @@
package pageman
import (
"os"
"runtime"
"sync"
"time"
)
type Guard struct {
mu sync.Mutex
buffers []*SecureBuffer
interval time.Duration
stop chan struct{}
stopped bool
}
func NewGuard(interval time.Duration) *Guard {
return &Guard{
interval: interval,
stop: make(chan struct{}),
}
}
func (g *Guard) Watch(buf *SecureBuffer) {
g.mu.Lock()
defer g.mu.Unlock()
g.buffers = append(g.buffers, buf)
}
func (g *Guard) Start() {
go g.loop()
}
func (g *Guard) Stop() {
g.mu.Lock()
defer g.mu.Unlock()
if g.stopped {
return
}
g.stopped = true
close(g.stop)
}
func (g *Guard) loop() {
ticker := time.NewTicker(g.interval)
defer ticker.Stop()
var prevMaps []byte
for {
select {
case <-g.stop:
return
case <-ticker.C:
g.enforceAutoLock()
if runtime.GOOS == "linux" {
prevMaps = g.checkMapsTampering(prevMaps)
}
}
}
}
func (g *Guard) enforceAutoLock() {
g.mu.Lock()
bufs := make([]*SecureBuffer, len(g.buffers))
copy(bufs, g.buffers)
g.mu.Unlock()
now := time.Now()
for _, buf := range bufs {
if buf.IsClosed() || buf.IsLocked() {
continue
}
if now.Sub(buf.LastAccess()) > buf.AutoLockDuration() {
buf.forceLock()
}
}
}
func (g *Guard) checkMapsTampering(prev []byte) []byte {
current, err := os.ReadFile("/proc/self/maps")
if err != nil {
return prev
}
if prev != nil && !equalBytes(prev, current) {
g.mu.Lock()
for _, buf := range g.buffers {
if !buf.IsClosed() && !buf.IsLocked() {
buf.forceLock()
}
}
g.mu.Unlock()
}
result := make([]byte, len(current))
copy(result, current)
return result
}
func equalBytes(a, b []byte) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
+216
View File
@@ -0,0 +1,216 @@
package pageman
import (
"crypto/rand"
"errors"
"sync"
"time"
)
var (
ErrAlreadyLocked = errors.New("pageman: buffer already locked")
ErrAlreadyUnlocked = errors.New("pageman: buffer already unlocked")
ErrClosed = errors.New("pageman: buffer closed")
)
type SecureBuffer struct {
mu sync.Mutex
data []byte
encrypted []byte
key [32]byte
locked bool
closed bool
lastAccess time.Time
autoLock time.Duration
}
func NewSecureBuffer(initialData []byte, autoLock time.Duration) *SecureBuffer {
sb := &SecureBuffer{
data: make([]byte, len(initialData)),
autoLock: autoLock,
lastAccess: time.Now(),
}
copy(sb.data, initialData)
if _, err := rand.Read(sb.key[:]); err != nil {
panic("pageman: failed to generate key: " + err.Error())
}
sb.encryptInPlace()
sb.locked = true
for i := range initialData {
initialData[i] = 0
}
return sb
}
func (sb *SecureBuffer) Lock() error {
sb.mu.Lock()
defer sb.mu.Unlock()
if sb.closed {
return ErrClosed
}
if sb.locked {
return ErrAlreadyLocked
}
sb.encryptInPlace()
sb.locked = true
return nil
}
func (sb *SecureBuffer) Unlock() error {
sb.mu.Lock()
defer sb.mu.Unlock()
if sb.closed {
return ErrClosed
}
if !sb.locked {
return ErrAlreadyUnlocked
}
sb.decryptInPlace()
sb.locked = false
sb.lastAccess = time.Now()
return nil
}
func (sb *SecureBuffer) Access(fn func([]byte)) error {
sb.mu.Lock()
if sb.closed {
sb.mu.Unlock()
return ErrClosed
}
wasLocked := sb.locked
if wasLocked {
sb.decryptInPlace()
sb.locked = false
}
sb.lastAccess = time.Now()
fn(sb.data)
if wasLocked {
sb.encryptInPlace()
sb.locked = true
}
sb.mu.Unlock()
return nil
}
func (sb *SecureBuffer) Close() {
sb.mu.Lock()
defer sb.mu.Unlock()
if sb.closed {
return
}
sb.zeroSlice(sb.data)
sb.zeroSlice(sb.encrypted)
sb.zeroKey()
sb.data = nil
sb.encrypted = nil
sb.locked = true
sb.closed = true
}
func (sb *SecureBuffer) IsLocked() bool {
sb.mu.Lock()
defer sb.mu.Unlock()
return sb.locked
}
func (sb *SecureBuffer) IsClosed() bool {
sb.mu.Lock()
defer sb.mu.Unlock()
return sb.closed
}
func (sb *SecureBuffer) LastAccess() time.Time {
sb.mu.Lock()
defer sb.mu.Unlock()
return sb.lastAccess
}
func (sb *SecureBuffer) AutoLockDuration() time.Duration {
return sb.autoLock
}
func (sb *SecureBuffer) forceLock() {
sb.mu.Lock()
defer sb.mu.Unlock()
if sb.closed || sb.locked {
return
}
sb.encryptInPlace()
sb.locked = true
}
func (sb *SecureBuffer) encryptInPlace() {
if len(sb.data) == 0 {
return
}
sb.encrypted = make([]byte, len(sb.data))
for i, b := range sb.data {
sb.encrypted[i] = b ^ sb.key[i%32]
}
for i := 0; i < 32; i++ {
sb.key[i] = rotateKey(sb.key[i], i)
}
sb.zeroSlice(sb.data)
sb.data = nil
}
func (sb *SecureBuffer) decryptInPlace() {
if len(sb.encrypted) == 0 {
return
}
for i := 0; i < 32; i++ {
sb.key[i] = unrotateKey(sb.key[i], i)
}
sb.data = make([]byte, len(sb.encrypted))
for i, b := range sb.encrypted {
sb.data[i] = b ^ sb.key[i%32]
}
sb.zeroSlice(sb.encrypted)
sb.encrypted = nil
}
func (sb *SecureBuffer) zeroSlice(b []byte) {
for i := range b {
b[i] = 0
}
}
func (sb *SecureBuffer) zeroKey() {
for i := range sb.key {
sb.key[i] = 0
}
}
func rotateKey(b byte, pos int) byte {
shift := uint((pos*3 + 7) % 8)
return b<<shift | b>>(8-shift)
}
func unrotateKey(b byte, pos int) byte {
shift := uint((pos*3 + 7) % 8)
return b>>shift | b<<(8-shift)
}