Skip to content

Instantly share code, notes, and snippets.

@arunk-s
Created January 29, 2015 19:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save arunk-s/bfcdc1159a4b07b41b02 to your computer and use it in GitHub Desktop.
Save arunk-s/bfcdc1159a4b07b41b02 to your computer and use it in GitHub Desktop.
AuditIsEnabled in Blocking Mode
package netlinkAudit
import (
"bytes"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"strconv"
"strings"
"sync/atomic"
"syscall"
"unsafe"
)
var ParsedResult AuditStatus
var nextSeqNr uint32
var rulesRetrieved AuditRuleData
type AuditStatus struct {
Mask uint32 /* Bit mask for valid entries */
Enabled uint32 /* 1 = enabled, 0 = disabled */
Failure uint32 /* Failure-to-log action */
Pid uint32 /* pid of auditd process */
Rate_limit uint32 /* messages rate limit (per second) */
Backlog_limit uint32 /* waiting messages limit */
Lost uint32 /* messages lost */
Backlog uint32 /* messages waiting in queue */
}
type AuditRuleData struct {
Flags uint32 /* AUDIT_PER_{TASK,CALL}, AUDIT_PREPEND */
Action uint32 /* AUDIT_NEVER, AUDIT_POSSIBLE, AUDIT_ALWAYS */
Field_count uint32
Mask [AUDIT_BITMASK_SIZE]uint32 /* syscall(s) affected */
Fields [AUDIT_MAX_FIELDS]uint32
Values [AUDIT_MAX_FIELDS]uint32
Fieldflags [AUDIT_MAX_FIELDS]uint32
Buflen uint32 /* total length of string fields */
Buf []byte //[0]byte /* string fields buffer */
}
type NetlinkSocket struct {
fd int
lsa syscall.SockaddrNetlink
}
type NetlinkAuditRequest struct {
Header syscall.NlMsghdr
Data []byte
}
// for config
type CMap struct {
Name string
Id int
}
//for fieldtab
type FMap struct {
Name string
Fieldid float64
}
// for config
type Config struct {
Xmap []CMap
}
type Field struct {
Fieldmap []FMap
}
func nativeEndian() binary.ByteOrder {
var x uint32 = 0x01020304
if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
return binary.BigEndian
}
return binary.LittleEndian
}
func (rule *AuditRuleData) ToWireFormat() []byte {
newbuff := make([]byte, int(unsafe.Sizeof(*rule))+int(rule.Buflen))
*(*uint32)(unsafe.Pointer(&newbuff[0:4][0])) = rule.Flags
*(*uint32)(unsafe.Pointer(&newbuff[4:8][0])) = rule.Action
*(*uint32)(unsafe.Pointer(&newbuff[8:12][0])) = rule.Field_count
*(*[AUDIT_BITMASK_SIZE]uint32)(unsafe.Pointer(&newbuff[12:268][0])) = rule.Mask
*(*[AUDIT_MAX_FIELDS]uint32)(unsafe.Pointer(&newbuff[268:524][0])) = rule.Fields
*(*[AUDIT_MAX_FIELDS]uint32)(unsafe.Pointer(&newbuff[524:780][0])) = rule.Values
*(*[AUDIT_MAX_FIELDS]uint32)(unsafe.Pointer(&newbuff[780:1036][0])) = rule.Fieldflags
*(*uint32)(unsafe.Pointer(&newbuff[1036:1040][0])) = rule.Buflen
copy(newbuff[1040:1040+rule.Buflen], rule.Buf[:])
return newbuff
}
//recvfrom in go takes only a byte [] to put the data recieved from the kernel that removes the need
//for having a separate audit_reply Struct for recieving data from kernel.
func (rr *NetlinkAuditRequest) ToWireFormat() []byte {
b := make([]byte, rr.Header.Len)
*(*uint32)(unsafe.Pointer(&b[0:4][0])) = rr.Header.Len
*(*uint16)(unsafe.Pointer(&b[4:6][0])) = rr.Header.Type
*(*uint16)(unsafe.Pointer(&b[6:8][0])) = rr.Header.Flags
*(*uint32)(unsafe.Pointer(&b[8:12][0])) = rr.Header.Seq
*(*uint32)(unsafe.Pointer(&b[12:16][0])) = rr.Header.Pid
b = append(b[:16], rr.Data[:]...) //Important b[:16]
return b
}
func newNetlinkAuditRequest(proto, family, sizeofData int) *NetlinkAuditRequest {
rr := &NetlinkAuditRequest{}
rr.Header.Len = uint32(syscall.NLMSG_HDRLEN + sizeofData)
rr.Header.Type = uint16(proto)
rr.Header.Flags = syscall.NLM_F_REQUEST | syscall.NLM_F_ACK
rr.Header.Seq = atomic.AddUint32(&nextSeqNr, 1) //Autoincrementing Sequence
return rr
// return rr.ToWireFormat()
}
// Round the length of a netlink message up to align it properly.
func nlmAlignOf(msglen int) int {
return (msglen + syscall.NLMSG_ALIGNTO - 1) & ^(syscall.NLMSG_ALIGNTO - 1)
}
// Parse a byte stream to an array of NetlinkMessage structs
func ParseAuditNetlinkMessage(b []byte) ([]syscall.NetlinkMessage, error) {
var msgs []syscall.NetlinkMessage
h, dbuf, dlen, err := netlinkMessageHeaderAndData(b)
if err != nil {
log.Println("Error in parsing")
return nil, err
}
m := syscall.NetlinkMessage{Header: *h, Data: dbuf[:int(h.Len) /* -syscall.NLMSG_HDRLEN*/]}
msgs = append(msgs, m)
b = b[dlen:]
return msgs, nil
}
// Internal Function, uses unsafe pointer conversions for separating Netlink Header and the Data appended with it
func netlinkMessageHeaderAndData(b []byte) (*syscall.NlMsghdr, []byte, int, error) {
h := (*syscall.NlMsghdr)(unsafe.Pointer(&b[0]))
if int(h.Len) < syscall.NLMSG_HDRLEN || int(h.Len) > len(b) {
foo := int32(nativeEndian().Uint32(b[0:4]))
log.Println("Headerlength with ", foo, b[0]) //bug!
log.Println("Error due to....HDRLEN:", syscall.NLMSG_HDRLEN, " Header Length:", h.Len, " Length of BYTE Array:", len(b))
return nil, nil, 0, syscall.EINVAL
}
return h, b[syscall.NLMSG_HDRLEN:], nlmAlignOf(int(h.Len)), nil
}
//Connect with kernel space and is to be used for all further socket communication
func GetNetlinkSocket() (*NetlinkSocket, error) {
fd, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW, syscall.NETLINK_AUDIT)
if err != nil {
return nil, err
}
s := &NetlinkSocket{
fd: fd,
}
s.lsa.Family = syscall.AF_NETLINK
s.lsa.Groups = 0
s.lsa.Pid = 0 //Kernel space pid is always set to be 0
if err := syscall.Bind(fd, &s.lsa); err != nil {
syscall.Close(fd)
return nil, err
}
return s, nil
}
//To end the socket conncetion
func (s *NetlinkSocket) Close() {
syscall.Close(s.fd)
}
// Wrapper for Sendto
func (s *NetlinkSocket) Send(request *NetlinkAuditRequest) error {
if err := syscall.Sendto(s.fd, request.ToWireFormat(), 0, &s.lsa); err != nil {
return err
}
return nil
}
// Wrapper for Recvfrom
func (s *NetlinkSocket) Receive(bytesize int, block int) ([]syscall.NetlinkMessage, error) {
rb := make([]byte, bytesize)
nr, _, err := syscall.Recvfrom(s.fd, rb, 0|block)
//nr, _, err := syscall.Recvfrom(s, rb, syscall.MSG_PEEK|syscall.MSG_DONTWAIT)
if err != nil {
return nil, err
}
if nr < syscall.NLMSG_HDRLEN {
return nil, syscall.EINVAL
}
rb = rb[:nr]
return ParseAuditNetlinkMessage(rb)
}
//HandleAck ?
func AuditGetReply(s *NetlinkSocket, bytesize, block int, seq uint32) error {
done:
for {
msgs, err := s.Receive(bytesize, block) //ParseAuditNetlinkMessage(rb)
if err != nil {
return err
}
for _, m := range msgs {
lsa, err := syscall.Getsockname(s.fd)
if err != nil {
return err
}
switch v := lsa.(type) {
case *syscall.SockaddrNetlink:
if m.Header.Seq != seq {
return fmt.Errorf("Wrong Seq nr %d, expected %d", m.Header.Seq, seq)
}
if m.Header.Pid != v.Pid {
return fmt.Errorf("Wrong pid %d, expected %d", m.Header.Pid, v.Pid)
}
default:
return syscall.EINVAL
}
if m.Header.Type == syscall.NLMSG_DONE {
break done
}
if m.Header.Type == syscall.NLMSG_ERROR {
error := int32(nativeEndian().Uint32(m.Data[0:4]))
if error == 0 {
log.Println("Acknowledged!!")
break done
} else {
log.Println("NLMSG_ERROR Received..")
}
break done
}
if m.Header.Type == AUDIT_GET {
log.Println("AUDIT_GET")
break done
}
}
}
return nil
}
// Sends a message to kernel to turn on audit
func AuditSetEnabled(s *NetlinkSocket) error {
var status AuditStatus
status.Enabled = 1
status.Mask = AUDIT_STATUS_ENABLED
buff := new(bytes.Buffer)
err := binary.Write(buff, nativeEndian(), status)
if err != nil {
log.Println("binary.Write failed:", err)
return err
}
wb := newNetlinkAuditRequest(AUDIT_SET, syscall.AF_NETLINK, int(unsafe.Sizeof(status)))
wb.Data = append(wb.Data[:], buff.Bytes()[:]...)
if err := s.Send(wb); err != nil {
return err
}
// Receiving IN JUST ONE TRY
err = AuditGetReply(s, syscall.Getpagesize(), 0, wb.Header.Seq)
if err != nil {
return err
}
return nil
}
// Sends a signal to kernel to check if Audit is enabled
func AuditIsEnabled(s *NetlinkSocket) error {
wb := newNetlinkAuditRequest(AUDIT_GET, syscall.AF_NETLINK, 0)
if err := s.Send(wb); err != nil {
return err
}
done:
for {
//Make the rb byte bigger because of large messages from Kernel doesn't fit in 4096
msgs, err := s.Receive(MAX_AUDIT_MESSAGE_LENGTH,0) //ADD syscall.MSG_DONWAIT
if err != nil {
return err
}
for _, m := range msgs {
lsa, er := syscall.Getsockname(s.fd)
if er != nil {
return nil
}
switch v := lsa.(type) {
case *syscall.SockaddrNetlink:
if m.Header.Seq != uint32(wb.Header.Seq) {
return fmt.Errorf("Wrong Seq nr %d, expected %d", m.Header.Seq, wb.Header.Seq)
}
if m.Header.Pid != v.Pid {
return fmt.Errorf("Wrong pid %d, expected %d", m.Header.Pid, v.Pid)
}
default:
return syscall.EINVAL
}
if m.Header.Type == syscall.NLMSG_DONE {
log.Println("Done")
break done
}
if m.Header.Type == syscall.NLMSG_ERROR {
log.Println("NLMSG_ERROR Received..")
}
if m.Header.Type == AUDIT_GET {
//Convert the data part written to AuditStatus struct
b := m.Data[:]
// h := (*AuditStatus)(unsafe.Pointer(&b[0])) Unsafe Method avoided
buf := bytes.NewBuffer(b)
var dumm AuditStatus
err = binary.Read(buf, nativeEndian(), &dumm)
if err != nil {
log.Println("binary.Read failed:", err)
return err
}
ParsedResult = dumm
break done
}
}
}
return nil
}
// Sends a message to kernel for setting of program pid
func AuditSetPid(s *NetlinkSocket, pid uint32 /*,Wait mode WAIT_YES | WAIT_NO */) error {
var status AuditStatus
status.Mask = AUDIT_STATUS_PID
status.Pid = pid
buff := new(bytes.Buffer)
err := binary.Write(buff, nativeEndian(), status)
if err != nil {
log.Println("binary.Write failed:", err)
return err
}
wb := newNetlinkAuditRequest(AUDIT_SET, syscall.AF_NETLINK, int(unsafe.Sizeof(status)))
wb.Data = append(wb.Data[:], buff.Bytes()[:]...)
if err := s.Send(wb); err != nil {
return err
}
err = AuditGetReply(s, syscall.Getpagesize(), 0, wb.Header.Seq)
if err != nil {
return err
}
//Polling in GO Is it needed ?
return nil
}
func auditWord(nr int) uint32 {
audit_word := (uint32)((nr) / 32)
return (uint32)(audit_word)
}
func auditBit(nr int) uint32 {
audit_bit := 1 << ((uint32)(nr) - auditWord(nr)*32)
return (uint32)(audit_bit)
}
// Make changes in the rule struct according to system call number
func AuditRuleSyscallData(rule *AuditRuleData, scall int) error {
word := auditWord(scall)
bit := auditBit(scall)
if word >= AUDIT_BITMASK_SIZE-1 {
return fmt.Errorf("Word Size greater than AUDIT_BITMASK_SIZE")
}
rule.Mask[word] |= bit
return nil
}
/*
Requires More work
func AuditWatchRuleData(s *NetlinkSocket, rule *AuditRuleData, path []byte) error {
rule.Flags = uint32(AUDIT_FILTER_EXIT)
rule.Action = uint32(AUDIT_ALWAYS)
// set mask
rule.Field_count = uint32(2)
rule.Fields[0] = uint32(105)
rule.Values[0] = uint32(len(path))
rule.Fieldflags[0] = uint32(AUDIT_EQUAL)
rule.Buflen = uint32(len(path))
rule.Buf = append(rule.Buf[:], path[:]...)
buff := new(bytes.Buffer)
err := binary.Write(buff, nativeEndian(), *rule)
if err != nil {
log.Println("binary.Write failed:", err)
return err
}
wb := newNetlinkAuditRequest(AUDIT_ADD_RULE, syscall.AF_NETLINK, int(buff.Len())+int(rule.Buflen))
wb.Data = append(wb.Data[:], buff.Bytes()[:]...)
if err := s.Send(wb); err != nil {
return err
}
return nil
}
*/
func AuditSetRateLimit(s *NetlinkSocket, limit int) error {
var foo AuditStatus
foo.Mask = AUDIT_STATUS_RATE_LIMIT
foo.Rate_limit = (uint32)(limit)
buff := new(bytes.Buffer)
err := binary.Write(buff, nativeEndian(), foo)
if err != nil {
log.Println("binary.Write failed:", err)
return err
}
wb := newNetlinkAuditRequest(AUDIT_SET, syscall.AF_NETLINK, int(unsafe.Sizeof(foo)))
wb.Data = append(wb.Data[:], buff.Bytes()[:]...)
if err := s.Send(wb); err != nil {
return err
}
err = AuditGetReply(s, syscall.Getpagesize(), 0, wb.Header.Seq)
if err != nil {
return err
}
return nil
}
func AuditSetBacklogLimit(s *NetlinkSocket, limit int) error {
var foo AuditStatus
foo.Mask = AUDIT_STATUS_BACKLOG_LIMIT
foo.Backlog_limit = (uint32)(limit)
buff := new(bytes.Buffer)
err := binary.Write(buff, nativeEndian(), foo)
if err != nil {
log.Println("binary.Write failed:", err)
return err
}
wb := newNetlinkAuditRequest(AUDIT_SET, syscall.AF_NETLINK, int(unsafe.Sizeof(foo)))
wb.Data = append(wb.Data[:], buff.Bytes()[:]...)
if err := s.Send(wb); err != nil {
return err
}
err = AuditGetReply(s, syscall.Getpagesize(), 0, wb.Header.Seq)
if err != nil {
return err
}
return nil
}
var errEntryDep = errors.New("Use of entry filter is deprecated")
func AuditAddRuleData(s *NetlinkSocket, rule *AuditRuleData, flags int, action int) error {
if flags == AUDIT_FILTER_ENTRY {
log.Println("Use of entry filter is deprecated")
return errEntryDep
}
rule.Flags = uint32(flags)
rule.Action = uint32(action)
// Using unsafe for conversion
newbuff := rule.ToWireFormat()
// Following method avoided as it require the 0 byte array to be fixed size array
// buff := new(bytes.Buffer)
// err := binary.Write(buff, nativeEndian(), *rule)
// if err != nil {
// log.Println("binary.Write failed:", err)
// return err
// }
// wb := newNetlinkAuditRequest(AUDIT_ADD_RULE, syscall.AF_NETLINK, int(buff.Len())+int(rule.Buflen))
// wb.Data = append(wb.Data[:], buff.Bytes()[:]...)
newwb := newNetlinkAuditRequest(AUDIT_ADD_RULE, syscall.AF_NETLINK, len(newbuff) /*+int(rule.Buflen)*/) //Length of newbuff takes care of Rule.buf too
newwb.Data = append(newwb.Data[:], newbuff[:]...)
var err error
if err = s.Send(newwb); err != nil {
return err
}
if err != nil {
log.Println("Error sending add rule data request")
return err
}
return nil
}
func isDone(msgchan chan string, errchan chan error, done <-chan bool) bool {
var d bool
select {
case d = <-done:
close(msgchan)
close(errchan)
default:
}
return d
}
//For Debugging Purposes
func GetreplyWithoutSync(s *NetlinkSocket) {
f, err := os.OpenFile("log", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0660)
if err != nil {
log.Println("Error Creating File!!")
return
}
defer f.Close()
for {
rb := make([]byte, MAX_AUDIT_MESSAGE_LENGTH)
nr, _, err := syscall.Recvfrom(s.fd, rb, 0)
if err != nil {
log.Println("Error While Recieving !!")
continue
}
if nr < syscall.NLMSG_HDRLEN {
log.Println("Message Too Short!!")
continue
}
rb = rb[:nr]
msgs, err := ParseAuditNetlinkMessage(rb)
if err != nil {
log.Println("Not Parsed Successfuly !!")
continue
}
for _, m := range msgs {
//Decide on various message Types
if m.Header.Type == syscall.NLMSG_DONE {
log.Println("Done")
} else if m.Header.Type == syscall.NLMSG_ERROR {
err := int32(nativeEndian().Uint32(m.Data[0:4]))
if err == 0 {
//Acknowledgement from kernel
log.Println("Ack")
} else {
log.Println("NLMSG_ERROR...")
}
} else if m.Header.Type == AUDIT_GET {
log.Println("AUDIT_GET")
} else if m.Header.Type == AUDIT_FIRST_USER_MSG {
log.Println("AUDIT_FIRST_USER_MSG")
} else if m.Header.Type == AUDIT_SYSCALL {
log.Println("Syscall Event")
log.Println(string(m.Data[:]))
_, err := f.WriteString(string(m.Data[:]) + "\n")
if err != nil {
log.Println("Writing Error!!")
}
} else if m.Header.Type == AUDIT_CWD {
log.Println("CWD Event")
log.Println(string(m.Data[:]))
_, err := f.WriteString(string(m.Data[:]) + "\n")
if err != nil {
log.Println("Writing Error!!")
}
} else if m.Header.Type == AUDIT_PATH {
log.Println("Path Event")
log.Println(string(m.Data[:]))
_, err := f.WriteString(string(m.Data[:]) + "\n")
if err != nil {
log.Println("Writing Error!!")
}
} else if m.Header.Type == AUDIT_EOE {
log.Println("Event Ends ", string(m.Data[:]))
} else if m.Header.Type == AUDIT_CONFIG_CHANGE {
log.Println("Config Change ", string(m.Data[:]))
_, err := f.WriteString(string(m.Data[:]) + "\n")
if err != nil {
log.Println("Writing Error!!")
}
} else {
log.Println("Unknown: ", m.Header.Type)
}
}
}
}
// Receives messages from Kernel and forwards to channels
func Getreply(s *NetlinkSocket, done <-chan bool, msgchan chan string, errchan chan error) {
for {
rb := make([]byte, MAX_AUDIT_MESSAGE_LENGTH)
nr, _, err := syscall.Recvfrom(s.fd, rb, 0)
if isDone(msgchan, errchan, done) {
return
}
if err != nil {
log.Println("Error While Recieving !!")
errchan <- err
continue
}
if nr < syscall.NLMSG_HDRLEN {
log.Println("Message Too Short!!")
errchan <- syscall.EINVAL
continue
}
rb = rb[:nr]
msgs, err := ParseAuditNetlinkMessage(rb)
if err != nil {
log.Println("Not Parsed Successfuly !!")
errchan <- err
continue
}
for _, m := range msgs {
//Decide on various message Types
//Add more message Types
if m.Header.Type == syscall.NLMSG_DONE {
log.Println("Done")
} else if m.Header.Type == syscall.NLMSG_ERROR {
err := int32(nativeEndian().Uint32(m.Data[0:4]))
if err == 0 {
//Acknowledgement from kernel
log.Println("Ack")
} else {
log.Println("NLMSG_ERROR")
}
} else if m.Header.Type == AUDIT_GET {
log.Println("AUDIT_GET")
} else if m.Header.Type == AUDIT_FIRST_USER_MSG {
log.Println("AUDIT_FIRST_USER_MSG")
} else if m.Header.Type == AUDIT_SYSCALL {
msgchan <- ("type=SYSCALL " + "msg=" + string(m.Data[:]))
} else if m.Header.Type == AUDIT_CWD {
msgchan <- ("type=CWD " + "msg=" + string(m.Data[:]))
} else if m.Header.Type == AUDIT_PATH {
msgchan <- ("type=PATH " + "msg=" + string(m.Data[:]))
} else if m.Header.Type == AUDIT_EOE {
// log.Println("Event Ends ", string(m.Data[:]))
} else if m.Header.Type == AUDIT_CONFIG_CHANGE {
msgchan <- ("type=CONFIG_CHANGE " + "msg=" + string(m.Data[:]))
} else {
log.Println("Unknown: ", m.Header.Type)
}
}
}
}
/*
// List all rules
// TODO: this funcion needs a lot of work to print actual rules
func ListAllRules(s *NetlinkSocket) error {
wb := newNetlinkAuditRequest(AUDIT_LIST_RULES, syscall.AF_NETLINK, 0)
if err := s.Send(wb); err != nil {
log.Print("Error:", err)
return err
}
done:
for {
msgs, err := s.Receive(MAX_AUDIT_MESSAGE_LENGTH, syscall.MSG_DONTWAIT)
if err != nil {
log.Println("ERROR while receiving rules:", err)
return err
}
for _, m := range msgs {
lsa, er := syscall.Getsockname(s.fd)
if er != nil {
log.Println("ERROR:", er)
return err
}
switch v := lsa.(type) {
case *syscall.SockaddrNetlink:
if m.Header.Seq != uint32(wb.Header.Seq) {
return fmt.Errorf("Wrong Seq nr %d, expected %d", m.Header.Seq, wb.Header.Seq)
}
if m.Header.Pid != v.Pid {
return fmt.Errorf("Wrong pid %d, expected %d", m.Header.Pid, v.Pid)
}
default:
log.Println("ERROR:", syscall.EINVAL)
}
if m.Header.Type == syscall.NLMSG_DONE {
log.Println("All rules deleted")
break done
}
if m.Header.Type == syscall.NLMSG_ERROR {
log.Println("NLMSG_ERROR")
}
if m.Header.Type == AUDIT_LIST_RULES {
b := m.Data[:]
//Should revert to rule.ToWireFormat()
buf := bytes.NewBuffer(b)
var rules AuditRuleData
rules.Buf = make([]byte, 0)
err = binary.Read(buf, nativeEndian(), &rules)
if err != nil {
log.Println("binary.Read failed:", err)
return err
}
// TODO : save all rules to an array so delete all rules function can use this
rulesRetrieved = rules
}
}
}
}
*/
//Delete Rule Data Function
func AuditDeleteRuleData(s *NetlinkSocket, rule *AuditRuleData, flags uint32, action uint32) error {
var sizePurpose AuditRuleData
sizePurpose.Buf = make([]byte, 0)
if flags == AUDIT_FILTER_ENTRY {
log.Println("Entry Filters Deprecated!!")
return errEntryDep
}
rule.Flags = flags
rule.Action = action
newbuff := rule.ToWireFormat()
// buff := new(bytes.Buffer)
// err := binary.Write(buff, nativeEndian(), *rule)
// if err != nil {
// log.Println("binary.Write failed:", err)
// return err
// }
// wb := newNetlinkAuditRequest(AUDIT_DEL_RULE, syscall.AF_NETLINK, int(unsafe.Sizeof(sizePurpose))+int(rule.Buflen))
// wb.Data = append(wb.Data[:], buff.Bytes()[:]...)
newwb := newNetlinkAuditRequest(AUDIT_DEL_RULE, syscall.AF_NETLINK, len(newbuff) /*+int(rule.Buflen)*/)
newwb.Data = append(newwb.Data[:], newbuff[:]...)
if err := s.Send(newwb); err != nil {
return err
}
return nil
}
// This function Deletes all rules
func DeleteAllRules(s *NetlinkSocket) error {
wb := newNetlinkAuditRequest(AUDIT_LIST_RULES, syscall.AF_NETLINK, 0)
if err := s.Send(wb); err != nil {
log.Print("Error:", err)
return err
}
done:
for {
//Make the rb byte bigger because of large messages from Kernel doesn't fit in 4096
msgs, err := s.Receive(MAX_AUDIT_MESSAGE_LENGTH, syscall.MSG_DONTWAIT)
if err != nil {
log.Println("ERROR while receiving rules:", err)
return err
}
for _, m := range msgs {
lsa, er := syscall.Getsockname(s.fd)
if er != nil {
log.Println("ERROR:", er)
return er
}
switch v := lsa.(type) {
case *syscall.SockaddrNetlink:
if m.Header.Seq != uint32(wb.Header.Seq) {
return fmt.Errorf("Wrong Seq nr %d, expected %d", m.Header.Seq, wb.Header.Seq)
}
if m.Header.Pid != v.Pid {
return fmt.Errorf("Wrong pid %d, expected %d", m.Header.Pid, v.Pid)
}
}
if m.Header.Type == syscall.NLMSG_DONE {
log.Println("Deleting Done!")
break done
}
if m.Header.Type == syscall.NLMSG_ERROR {
log.Println("NLMSG_ERROR\n")
}
if m.Header.Type == AUDIT_LIST_RULES {
b := m.Data[:]
rules := (*AuditRuleData)(unsafe.Pointer(&b[0]))
//Sizeof rules is 1064 > 1056
//Error handling here ?
// log.Println(len(b), h)
// buf := bytes.NewBuffer(b)
// var rules AuditRuleData
// rules.Buf = make([]byte, 0)
// err = binary.Read(buf, nativeEndian(), &rules)
// if err != nil {
// log.Println("Binary Read Failed !!", err)
// return err
// }
err = AuditDeleteRuleData(s, rules, rules.Flags, rules.Action)
if err != nil {
return err
}
}
}
}
return nil
}
var _audit_permadded bool
var _audit_syscalladded bool
// Load x86 map and fieldtab.json
func loadSysMap_FieldTab(conf *Config, fieldmap *Field) error {
content2, err := ioutil.ReadFile("netlinkAudit/audit_x86_64.json")
if err != nil {
return err
}
content3, err := ioutil.ReadFile("netlinkAudit/fieldtab.json")
if err != nil {
return err
}
err = json.Unmarshal([]byte(content2), &conf)
if err != nil {
return err
}
err = json.Unmarshal([]byte(content3), &fieldmap)
if err != nil {
return err
}
return nil
}
//sets each rule after reading configuration file
func SetRules(s *NetlinkSocket) error {
//var rule AuditRuleData
//AuditWatchRuleData(s, &rule, []byte("/etc/passwd"))
// Load all rules
content, err := ioutil.ReadFile("netlinkAudit/audit.rules.json")
if err != nil {
log.Print("Error:", err)
return err
}
var rules interface{}
err = json.Unmarshal(content, &rules)
if err != nil {
log.Print("Error:", err)
return err
}
m := rules.(map[string]interface{})
if _, ok := m["delete"]; ok {
//First Delete All rules and then add rules
log.Println("Deleting all rules")
err := DeleteAllRules(s)
if err != nil {
log.Println("Error Deleting Rules!")
return err
}
}
var conf Config
var fieldmap Field
// Load x86 map and fieldtab.json
err = loadSysMap_FieldTab(&conf, &fieldmap)
if err != nil {
log.Println("Error :", err)
return err
}
for k, v := range m {
switch k {
case "custom_rule":
// Still Needed ?
vi := v.([]interface{})
for ruleNo := range vi {
rule := vi[ruleNo].(map[string]interface{})
for l, m := range rule {
switch l {
case "action":
//TODO: handle actions case here
action := m.([]interface{})
log.Println("actions are : ", action[0])
case "fields":
//TODO: handle fields case here
fields := m.([]interface{})
for _, q := range fields {
log.Println("fields are", q)
}
}
}
}
case "syscall_rules":
vi := v.([]interface{})
for sruleNo := range vi {
srule := vi[sruleNo].(map[string]interface{})
for l := range conf.Xmap {
if conf.Xmap[l].Name == srule["name"] {
// set rules
log.Println("setting syscall rule", conf.Xmap[l].Name)
var dd AuditRuleData
dd.Buf = make([]byte, 0)
err = AuditRuleSyscallData(&dd, conf.Xmap[l].Id)
if err == nil {
_audit_syscalladded = true
} else {
return err
}
actions := srule["action"].([]interface{})
//log.Println(actions)
//NOW APPLY ACTIONS ON SYSCALLS by separating the filters i.e exit from action i.e. always
action := 0
filter := 0
//This part supposes that actions and filters are written as always,exit or never,exit not viceversa
if actions[0] == "never" {
action = AUDIT_NEVER
} else if actions[0] == "possible" {
action = AUDIT_POSSIBLE
} else if actions[0] == "always" {
action = AUDIT_ALWAYS
} else {
action = -1
}
if actions[1] == "task" {
filter = AUDIT_FILTER_TASK
} else if actions[1] == "entry" {
log.Println("Support for Entry Filter is Deprecated!! Switching back to Exit filter")
filter = AUDIT_FILTER_EXIT
} else if actions[1] == "exit" {
filter = AUDIT_FILTER_EXIT
} else if actions[1] == "user" {
filter = AUDIT_FILTER_USER
} else if actions[1] == "exclude" {
filter = AUDIT_FILTER_EXCLUDE
} else {
filter = AUDIT_FILTER_UNSET
}
for _, field := range srule["fields"].([]interface{}) {
fieldval := field.(map[string]interface{})["value"]
op := field.(map[string]interface{})["op"]
fieldname := field.(map[string]interface{})["name"]
//log.Println(fieldval, op, fieldname)
var opval uint32
if op == "nt_eq" {
opval = AUDIT_NOT_EQUAL
} else if op == "gt_or_eq" {
opval = AUDIT_GREATER_THAN_OR_EQUAL
} else if op == "lt_or_eq" {
opval = AUDIT_LESS_THAN_OR_EQUAL
} else if op == "and_eq" {
opval = AUDIT_BIT_TEST
} else if op == "eq" {
opval = AUDIT_EQUAL
} else if op == "gt" {
opval = AUDIT_GREATER_THAN
} else if op == "lt" {
opval = AUDIT_LESS_THAN
} else if op == "and" {
opval = AUDIT_BIT_MASK
}
//Take appropriate action according to filters provided
err = AuditRuleFieldPairData(&dd, fieldval, opval, fieldname.(string), fieldmap, filter) // &AUDIT_BIT_MASK
if err != nil {
return err
}
}
// foo.Fields[foo.Field_count] = AUDIT_ARCH
// foo.Fieldflags[foo.Field_count] = AUDIT_EQUAL
// foo.Values[foo.Field_count] = AUDIT_ARCH_X86_64
// foo.Field_count++
// AuditAddRuleData(s, &foo, AUDIT_FILTER_EXIT, AUDIT_ALWAYS)
if filter != AUDIT_FILTER_UNSET {
AuditAddRuleData(s, &dd, filter, action)
} else {
return fmt.Errorf("Filters Not Set")
}
}
}
}
}
}
return nil
}
func AuditNameToFtype(name string, value *int) error {
content, err := ioutil.ReadFile("netlinkAudit/ftypetab.json")
if err != nil {
log.Print("Error:", err)
return err
}
var filemap interface{}
err = json.Unmarshal(content, &filemap)
if err != nil {
log.Print("Error:", err)
return err
}
m := filemap.(map[string]interface{})
for k, v := range m {
if k == name {
*value = int(v.(float64))
return nil
}
}
return fmt.Errorf("Filetype not found")
}
var (
errMaxField = errors.New("MAX Fields for AuditRuleData exceeded")
errNoStr = errors.New("No support for string values")
errUnset = errors.New("Unable to set value")
errNoExit = errors.New("Filter can only be used with AUDIT_EXIT")
errNoSys = errors.New("No syscall added")
errMaxLen = errors.New("MAX length Exceeded")
)
func AuditRuleFieldPairData(rule *AuditRuleData, fieldval interface{}, opval uint32, fieldname string, fieldmap Field, flags int) error {
if rule.Field_count >= (AUDIT_MAX_FIELDS - 1) {
log.Println("Max Fields Exceeded !!")
return errMaxField
}
var fieldid uint32
for f := range fieldmap.Fieldmap {
if fieldmap.Fieldmap[f].Name == fieldname {
//log.Println("Found :", fieldmap.Fieldmap[f])
fieldid = (uint32)(fieldmap.Fieldmap[f].Fieldid)
}
}
rule.Fields[rule.Field_count] = fieldid
rule.Fieldflags[rule.Field_count] = opval
log.Println("Going for", fieldname)
switch fieldid {
case AUDIT_UID, AUDIT_EUID, AUDIT_SUID, AUDIT_FSUID, AUDIT_LOGINUID, AUDIT_OBJ_UID, AUDIT_OBJ_GID:
if val, isInt := fieldval.(float64); isInt {
if val < 0 {
// For trimming "-" and evaluating th condition vlen >=2 (which is not needed)
valString := strconv.FormatInt((int64)(val), 10)
fieldvalUid := strings.Replace(valString, "-", "", -1)
a, err := strconv.Atoi(fieldvalUid)
if err != nil {
log.Println("Conversion not possible")
return err
} else {
rule.Values[rule.Field_count] = (uint32)(a)
}
} else {
rule.Values[rule.Field_count] = (uint32)(val)
}
} else if val, isString := fieldval.(string); isString {
if fieldval.(string) == "unset" {
rule.Values[rule.Field_count] = 4294967295
} else {
log.Println("No support for string values yet !", val)
return errNoStr
//Insert audit_name_to_uid(string,int * val)
}
} else {
log.Println("Error Setting Value:", fieldval)
return errUnset
}
case AUDIT_GID, AUDIT_EGID, AUDIT_SGID, AUDIT_FSGID:
//IF DIGITS THEN
if val, isInt := fieldval.(float64); isInt {
rule.Values[rule.Field_count] = (uint32)(val)
} else if val, isString := fieldval.(string); isString {
log.Println("No support for string values yet !", val)
return errNoStr
//audit_name_to_gid(string, sint*val)
} else {
log.Println("Error Setting Value:", fieldval)
return errUnset
}
case AUDIT_EXIT:
if flags != AUDIT_FILTER_EXIT {
return errNoExit
}
if val, isInt := fieldval.(float64); isInt {
if val < 0 {
// For trimming "-" and evaluating th condition vlen >=2 (which is not needed)
valString := strconv.FormatInt((int64)(val), 10)
fieldvalUid := strings.Replace(valString, "-", "", -1)
a, err := strconv.Atoi(fieldvalUid)
if err != nil {
return err
} else {
rule.Values[rule.Field_count] = (uint32)(a)
}
} else {
rule.Values[rule.Field_count] = (uint32)(val)
}
} else if val, isString := fieldval.(string); isString {
log.Println("No support for string values yet !", val)
return errNoStr
} else {
log.Println("Error Setting Value:", fieldval)
return errUnset
}
//TODO: String handling part
//else {
// rule->values[rule->field_count] = //SEE HERE
// audit_name_to_errno(v);
// if (rule->values[rule->field_count] == 0)
// return -15;
//}
//break;
case AUDIT_MSGTYPE:
if flags != AUDIT_FILTER_EXCLUDE && flags != AUDIT_FILTER_USER {
return fmt.Errorf("AUDIT_MSGTYPE can only be used with AUDIT_FILTER_EXCLUDE")
}
if val, isInt := fieldval.(float64); isInt {
rule.Values[rule.Field_count] = (uint32)(val)
} else if val, isString := fieldval.(string); isString {
log.Println("No support for string values yet !", val)
return errNoStr
} else {
log.Println("Error Setting Value:", fieldval)
return errUnset
}
//Strings
case AUDIT_OBJ_USER, AUDIT_OBJ_ROLE, AUDIT_OBJ_TYPE, AUDIT_OBJ_LEV_LOW, AUDIT_OBJ_LEV_HIGH, AUDIT_WATCH, AUDIT_DIR:
/* Watch & object filtering is invalid on anything
* but exit */
if flags != AUDIT_FILTER_EXIT {
return errNoExit
}
if fieldid == AUDIT_WATCH || fieldid == AUDIT_DIR {
_audit_permadded = true
}
fallthrough //IMP
case AUDIT_SUBJ_USER, AUDIT_SUBJ_ROLE, AUDIT_SUBJ_TYPE, AUDIT_SUBJ_SEN, AUDIT_SUBJ_CLR, AUDIT_FILTERKEY:
//IF And only if a syscall is added or a permisission is added then this field should be set
//MORE Debugging Required
if fieldid == AUDIT_FILTERKEY && !(_audit_syscalladded || _audit_permadded) {
return errNoSys
}
if val, isString := fieldval.(string); isString {
valbyte := []byte(val)
vlen := len(valbyte)
if fieldid == AUDIT_FILTERKEY && vlen > AUDIT_MAX_KEY_LEN {
return errMaxLen
} else if vlen > PATH_MAX {
return errMaxLen
}
rule.Values[rule.Field_count] = (uint32)(vlen)
rule.Buflen = rule.Buflen + (uint32)(vlen)
// log.Println(unsafe.Sizeof(*rule), vlen)
//Now append the key value with the rule buffer space
//May need to reallocate memory to rule.Buf i.e. the 0 size byte array, append will take care of that
rule.Buf = append(rule.Buf, valbyte[:]...)
// log.Println(int(unsafe.Sizeof(*rule)), *rule)
}
case AUDIT_ARCH:
if _audit_syscalladded == false {
return errNoSys
} else {
//AUDIT_ARCH_X86_64 is made specifically for Mozilla Heka purpose, please make changes as per required
if _, isInt := fieldval.(float64); isInt {
rule.Values[rule.Field_count] = AUDIT_ARCH_X86_64
} else if _, isString := fieldval.(string); isString {
return errNoStr
} else {
return errUnset
}
}
case AUDIT_PERM:
//DECIDE ON VARIOUS ERROR TYPES
if flags != AUDIT_FILTER_EXIT {
return errNoExit
} else if opval != AUDIT_EQUAL {
return fmt.Errorf("Operator can only be AUDIT_EQUAL in case of AUDIT_PERM")
} else {
if val, isString := fieldval.(string); isString {
var i, vallen int
vallen = len(val)
var permval uint32
if vallen > 4 {
return errMaxLen
}
lowerval := strings.ToLower(val)
for i = 0; i < vallen; i++ {
switch lowerval[i] {
case 'r':
permval |= AUDIT_PERM_READ
case 'w':
permval |= AUDIT_PERM_WRITE
case 'x':
permval |= AUDIT_PERM_EXEC
case 'a':
permval |= AUDIT_PERM_ATTR
default:
return fmt.Errorf(" %s is not found as permission", lowerval[i])
}
}
rule.Values[rule.Field_count] = permval
_audit_permadded = true
}
}
case AUDIT_FILETYPE:
if val, isString := fieldval.(string); isString {
if !(flags == AUDIT_FILTER_EXIT) && flags == AUDIT_FILTER_ENTRY {
return fmt.Errorf("Flag can only be AUDIT_EXIT in case of AUDIT_FILETYPE")
}
var fileval int
err := AuditNameToFtype(val, &fileval)
if err != nil {
return err
}
rule.Values[rule.Field_count] = uint32(fileval)
if (int)(rule.Values[rule.Field_count]) < 0 {
return syscall.EINVAL
}
} else {
return fmt.Errorf("Numbers as filetypes")
}
case AUDIT_ARG0, AUDIT_ARG1, AUDIT_ARG2, AUDIT_ARG3:
if val, isInt := fieldval.(float64); isInt {
if val < 0 {
// For trimming "-" and evaluating th condition vlen >=2 (which is not needed)
valString := strconv.FormatInt((int64)(val), 10)
fieldvalUid := strings.Replace(valString, "-", "", -1)
a, err := strconv.Atoi(fieldvalUid)
if err != nil {
return err
} else {
rule.Values[rule.Field_count] = (uint32)(a)
}
} else {
rule.Values[rule.Field_count] = (uint32)(val)
}
} else if _, isString := fieldval.(string); isString {
return errNoStr
} else {
log.Println("Error Setting Value:", fieldval)
return errUnset
}
case AUDIT_DEVMAJOR, AUDIT_INODE, AUDIT_SUCCESS:
if flags != AUDIT_FILTER_EXIT {
return errNoExit
}
fallthrough
default:
if fieldid == AUDIT_INODE {
if !(opval == AUDIT_NOT_EQUAL || opval == AUDIT_EQUAL) {
return fmt.Errorf("OP can only be AUDIT_NOT_EQUAL or AUDIT_EQUAL")
}
}
if fieldid == AUDIT_PPID && !(flags == AUDIT_FILTER_EXIT || flags == AUDIT_FILTER_ENTRY) {
return fmt.Errorf("Flags can only be EXIT or ENTRY in case of AUDIT_PPID")
}
if val, isInt := fieldval.(float64); isInt {
if fieldid == AUDIT_INODE {
rule.Values[rule.Field_count] = (uint32)(val)
} else {
rule.Values[rule.Field_count] = (uint32)(val)
}
} else {
log.Println("Error Setting Value:", fieldval)
return errUnset
}
}
rule.Field_count++
return nil
}
/*
If further needed
var ErrStrings = []string{"E2BIG", "EACCES", "EADDRINUSE", "EADDRNOTAVAIL", "EADV", "EAFNOSUPPORT", "EAGAIN", "EALREADY", "EBADE", "EBADF",
"EBADFD", "EBADMSG", "EBADR", "EBADRQC", "EBADSLT", "EBFONT", "EBUSY", "ECANCELED", "ECHILD", "ECHRNG",
"ECOMM", "ECONNABORTED", "ECONNREFUSED", "ECONNRESET", "EDEADLK", "EDEADLOCK", "EDESTADDRREQ", "EDOM", "EDOTDOT", "EDQUOT",
"EEXIST", "EFAULT", "EFBIG", "EHOSTDOWN", "EHOSTUNREACH", "EIDRM", "EILSEQ", "EINPROGRESS", "EINTR", "EINVAL",
"EIO", "EISCONN", "EISDIR", "EISNAM", "EKEYEXPIRED", "EKEYREJECTED", "EKEYREVOKED", "EL2HLT", "EL2NSYNC", "EL3HLT",
"EL3RST", "ELIBACC", "ELIBBAD", "ELIBEXEC", "ELIBMAX", "ELIBSCN", "ELNRNG", "ELOOP", "EMEDIUMTYPE", "EMFILE",
"EMLINK", "EMSGSIZE", "EMULTIHOP", "ENAMETOOLONG", "ENAVAIL", "ENETDOWN", "ENETRESET", "ENETUNREACH", "ENFILE", "ENOANO",
"ENOBUFS", "ENOCSI", "ENODATA", "ENODEV", "ENOENT", "ENOEXEC", "ENOKEY", "ENOLCK", "ENOLINK", "ENOMEDIUM",
"ENOMEM", "ENOMSG", "ENONET", "ENOPKG", "ENOPROTOOPT", "ENOSPC", "ENOSR", "ENOSTR", "ENOSYS", "ENOTBLK",
"ENOTCONN", "ENOTDIR", "ENOTEMPTY", "ENOTNAM", "ENOTRECOVERABLE", "ENOTSOCK", "ENOTTY", "ENOTUNIQ", "ENXIO", "EOPNOTSUPP",
"EOVERFLOW", "EOWNERDEAD", "EPERM", "EPFNOSUPPORT", "EPIPE", "EPROTO", "EPROTONOSUPPORT", "EPROTOTYPE", "ERANGE", "EREMCHG",
"EREMOTE", "EREMOTEIO", "ERESTART", "EROFS", "ESHUTDOWN", "ESOCKTNOSUPPORT", "ESPIPE", "ESRCH", "ESRMNT", "ESTALE",
"ESTRPIPE", "ETIME", "ETIMEDOUT", "ETOOMANYREFS", "ETXTBSY", "EUCLEAN", "EUNATCH", "EUSERS", "EWOULDBLOCK", "EXDEV",
"EXFULL"}
var ErrS2iI = []int{7, 13, 98, 99, 68, 97, 11, 114, 52, 9, 77, 74, 53, 56, 57, 59, 16, 125, 10, 44, 70, 103, 111, 104, 35, 35, 89, 33, 73, 122, 17, 14, 27, 112, 113, 43, 84, 115, 4, 22,
5, 106, 21, 120, 127, 129, 128, 51, 45, 46, 47, 79, 80, 83, 82, 81, 48, 40, 124, 24, 31, 90, 72, 36, 119, 100, 102, 101, 23, 55, 105, 50, 61, 19, 2, 8, 126, 37, 67, 123, 12, 42, 64, 65, 92, 28, 63, 60, 38, 15,
107, 20, 39, 118, 131, 88, 25, 76, 6, 95, 75, 130, 1, 96, 32, 71, 93, 91, 34, 78, 66, 121, 85, 30, 108, 94, 29, 3, 69, 116, 86, 62, 110, 109, 26, 117, 49, 87, 11, 18, 54}
var audit_elf uint = 0
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment