fortify/acl.go
Ophestra Umiker 7d96b0bf35
acl: fix memory leak in removeEntry
According to manpage acl_get_qualifier(3) the void * returned by this function could be allocated on the heap.

Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
2024-07-15 12:40:29 +09:00

177 lines
3.3 KiB
Go

package main
import (
"errors"
"fmt"
"syscall"
"unsafe"
)
//#include <stdlib.h>
//#include <sys/acl.h>
//#include <acl/libacl.h>
//#cgo linux LDFLAGS: -lacl
import "C"
const (
aclRead = C.ACL_READ
aclWrite = C.ACL_WRITE
aclExecute = C.ACL_EXECUTE
aclTypeDefault = C.ACL_TYPE_DEFAULT
aclTypeAccess = C.ACL_TYPE_ACCESS
aclUndefinedTag = C.ACL_UNDEFINED_TAG
aclUserObj = C.ACL_USER_OBJ
aclUser = C.ACL_USER
aclGroupObj = C.ACL_GROUP_OBJ
aclGroup = C.ACL_GROUP
aclMask = C.ACL_MASK
aclOther = C.ACL_OTHER
)
type acl struct {
val C.acl_t
freed bool
}
func aclUpdatePerm(path string, uid int, perms ...C.acl_perm_t) error {
// read acl from file
a, err := aclGetFile(path, aclTypeAccess)
if err != nil {
return err
}
// free acl on return if get is successful
defer a.free()
// remove existing entry
if err = a.removeEntry(aclUser, uid); err != nil {
return err
}
// create new entry if perms are passed
if len(perms) > 0 {
// create new acl entry
var e C.acl_entry_t
if _, err = C.acl_create_entry(&a.val, &e); err != nil {
return err
}
// get perm set of new entry
var p C.acl_permset_t
if _, err = C.acl_get_permset(e, &p); err != nil {
return err
}
// add target perms
for _, perm := range perms {
if _, err = C.acl_add_perm(p, perm); err != nil {
return err
}
}
// set perm set to new entry
if _, err = C.acl_set_permset(e, p); err != nil {
return err
}
// set user tag to new entry
if _, err = C.acl_set_tag_type(e, aclUser); err != nil {
return err
}
// set qualifier (uid) to new entry
if _, err = C.acl_set_qualifier(e, unsafe.Pointer(&uid)); err != nil {
return err
}
}
// calculate mask after update
if _, err = C.acl_calc_mask(&a.val); err != nil {
return err
}
// write acl to file
return a.setFile(path, aclTypeAccess)
}
func aclGetFile(path string, t C.acl_type_t) (*acl, error) {
p := C.CString(path)
a, err := C.acl_get_file(p, t)
C.free(unsafe.Pointer(p))
if errors.Is(err, syscall.ENODATA) {
err = nil
}
return &acl{val: a, freed: false}, err
}
func (a *acl) setFile(path string, t C.acl_type_t) error {
if C.acl_valid(a.val) != 0 {
return fmt.Errorf("invalid acl")
}
p := C.CString(path)
_, err := C.acl_set_file(p, t, a.val)
C.free(unsafe.Pointer(p))
return err
}
func (a *acl) removeEntry(tt C.acl_tag_t, tq int) error {
var e C.acl_entry_t
// get first entry
if r, err := C.acl_get_entry(a.val, C.ACL_FIRST_ENTRY, &e); err != nil {
return err
} else if r == 0 {
// return on acl with no entries
return nil
}
for {
if r, err := C.acl_get_entry(a.val, C.ACL_NEXT_ENTRY, &e); err != nil {
return err
} else if r == 0 {
// return on drained acl
return nil
}
var (
q int
t C.acl_tag_t
)
// get current entry tag type
if _, err := C.acl_get_tag_type(e, &t); err != nil {
return err
}
// get current entry qualifier
if rq, err := C.acl_get_qualifier(e); err != nil {
// neither ACL_USER nor ACL_GROUP
if errors.Is(err, syscall.EINVAL) {
continue
}
return err
} else {
q = *(*int)(rq)
C.acl_free(rq)
}
// delete on match
if t == tt && q == tq {
_, err := C.acl_delete_entry(a.val, e)
return err
}
}
}
func (a *acl) free() {
if a.freed {
panic("acl already freed")
}
C.acl_free(unsafe.Pointer(a.val))
a.freed = true
}