Cybersecurity-Projects/PROJECTS/intermediate/docker-security-audit/internal/proc/capabilities.go

310 lines
6.8 KiB
Go

/*
CarterPerez-dev | 2026
capabilities.go
CapabilitySet type for parsing and inspecting Linux process capability
bitmasks
Wraps the five Linux capability bitmasks (effective, permitted,
inheritable, bounding, ambient) with methods for testing individual
capabilities, listing active sets, and diffing against Docker's
default 14-capability set to find additions or drops.
Key exports:
CapabilitySet - holds all five capability bitmasks with bit-level
methods
HasCapability, HasDangerousCapabilities, HasCriticalCapabilities -
checks
GetAddedCapabilities, GetDroppedDefaultCapabilities,
HasOnlyDefaultCapabilities
ParseCapabilityMask, AllCapabilityNames - parsing and enumeration
utilities
Connects to:
rules/capabilities.go - IsDangerousCapability, IsCriticalCapability,
GetCapabilitySeverity
finding.go - uses Severity for GetCapabilitiesBySeverity threshold
checks
proc.go - CapabilitySet embedded in ProcessInfo.Capabilities
security.go - CapabilitySet embedded in SecurityProfile.Capabilities
*/
package proc
import (
"fmt"
"github.com/CarterPerez-dev/docksec/internal/finding"
"github.com/CarterPerez-dev/docksec/internal/rules"
)
type CapabilitySet struct {
Effective uint64
Permitted uint64
Inheritable uint64
Bounding uint64
Ambient uint64
}
var capabilityBits = map[int]string{
0: "CAP_CHOWN",
1: "CAP_DAC_OVERRIDE",
2: "CAP_DAC_READ_SEARCH",
3: "CAP_FOWNER",
4: "CAP_FSETID",
5: "CAP_KILL",
6: "CAP_SETGID",
7: "CAP_SETUID",
8: "CAP_SETPCAP",
9: "CAP_LINUX_IMMUTABLE",
10: "CAP_NET_BIND_SERVICE",
11: "CAP_NET_BROADCAST",
12: "CAP_NET_ADMIN",
13: "CAP_NET_RAW",
14: "CAP_IPC_LOCK",
15: "CAP_IPC_OWNER",
16: "CAP_SYS_MODULE",
17: "CAP_SYS_RAWIO",
18: "CAP_SYS_CHROOT",
19: "CAP_SYS_PTRACE",
20: "CAP_SYS_PACCT",
21: "CAP_SYS_ADMIN",
22: "CAP_SYS_BOOT",
23: "CAP_SYS_NICE",
24: "CAP_SYS_RESOURCE",
25: "CAP_SYS_TIME",
26: "CAP_SYS_TTY_CONFIG",
27: "CAP_MKNOD",
28: "CAP_LEASE",
29: "CAP_AUDIT_WRITE",
30: "CAP_AUDIT_CONTROL",
31: "CAP_SETFCAP",
32: "CAP_MAC_OVERRIDE",
33: "CAP_MAC_ADMIN",
34: "CAP_SYSLOG",
35: "CAP_WAKE_ALARM",
36: "CAP_BLOCK_SUSPEND",
37: "CAP_AUDIT_READ",
38: "CAP_PERFMON",
39: "CAP_BPF",
40: "CAP_CHECKPOINT_RESTORE",
}
var capabilityNames = func() map[string]int {
m := make(map[string]int, len(capabilityBits))
for bit, name := range capabilityBits {
m[name] = bit
}
return m
}()
func (c *CapabilitySet) HasCapability(name string) bool {
bit, ok := capabilityNames[name]
if !ok {
return false
}
return (c.Effective & (1 << bit)) != 0
}
func (c *CapabilitySet) HasPermitted(name string) bool {
bit, ok := capabilityNames[name]
if !ok {
return false
}
return (c.Permitted & (1 << bit)) != 0
}
func (c *CapabilitySet) HasBounding(name string) bool {
bit, ok := capabilityNames[name]
if !ok {
return false
}
return (c.Bounding & (1 << bit)) != 0
}
func (c *CapabilitySet) HasAmbient(name string) bool {
bit, ok := capabilityNames[name]
if !ok {
return false
}
return (c.Ambient & (1 << bit)) != 0
}
func (c *CapabilitySet) ListEffective() []string {
return c.listCaps(c.Effective)
}
func (c *CapabilitySet) ListPermitted() []string {
return c.listCaps(c.Permitted)
}
func (c *CapabilitySet) ListBounding() []string {
return c.listCaps(c.Bounding)
}
func (c *CapabilitySet) ListAmbient() []string {
return c.listCaps(c.Ambient)
}
func (c *CapabilitySet) ListInheritable() []string {
return c.listCaps(c.Inheritable)
}
func (c *CapabilitySet) listCaps(mask uint64) []string {
var caps []string
for bit, name := range capabilityBits {
if (mask & (1 << bit)) != 0 {
caps = append(caps, name)
}
}
return caps
}
func (c *CapabilitySet) IsFullyPrivileged() bool {
return c.Effective == 0x1ffffffffff || c.Effective == 0xffffffffffffffff
}
func (c *CapabilitySet) HasDangerousCapabilities() bool {
for _, cap := range c.ListEffective() {
if rules.IsDangerousCapability(cap) {
return true
}
}
return false
}
func (c *CapabilitySet) HasCriticalCapabilities() bool {
for _, cap := range c.ListEffective() {
if rules.IsCriticalCapability(cap) {
return true
}
}
return false
}
func (c *CapabilitySet) GetDangerousCapabilities() []string {
var dangerous []string
for _, cap := range c.ListEffective() {
if rules.IsDangerousCapability(cap) {
dangerous = append(dangerous, cap)
}
}
return dangerous
}
func (c *CapabilitySet) GetCriticalCapabilities() []string {
var critical []string
for _, cap := range c.ListEffective() {
if rules.IsCriticalCapability(cap) {
critical = append(critical, cap)
}
}
return critical
}
func (c *CapabilitySet) GetCapabilitiesBySeverity(
minSeverity finding.Severity,
) []string {
var result []string
for _, cap := range c.ListEffective() {
severity := rules.GetCapabilitySeverity(cap)
if severity >= minSeverity {
result = append(result, cap)
}
}
return result
}
func (c *CapabilitySet) EffectiveCount() int {
return countBits(c.Effective)
}
func (c *CapabilitySet) PermittedCount() int {
return countBits(c.Permitted)
}
func (c *CapabilitySet) BoundingCount() int {
return countBits(c.Bounding)
}
func countBits(n uint64) int {
count := 0
for n != 0 {
count += int(n & 1)
n >>= 1
}
return count
}
func ParseCapabilityMask(hex string) (uint64, error) {
var mask uint64
_, err := fmt.Sscanf(hex, "%x", &mask)
return mask, err
}
func CapabilityNameToBit(name string) (int, bool) {
bit, ok := capabilityNames[name]
return bit, ok
}
func CapabilityBitToName(bit int) (string, bool) {
name, ok := capabilityBits[bit]
return name, ok
}
func AllCapabilityNames() []string {
names := make([]string, 0, len(capabilityBits))
for i := 0; i <= 40; i++ {
if name, ok := capabilityBits[i]; ok {
names = append(names, name)
}
}
return names
}
var defaultDockerCaps = map[string]struct{}{
"CAP_CHOWN": {},
"CAP_DAC_OVERRIDE": {},
"CAP_FSETID": {},
"CAP_FOWNER": {},
"CAP_MKNOD": {},
"CAP_NET_RAW": {},
"CAP_SETGID": {},
"CAP_SETUID": {},
"CAP_SETFCAP": {},
"CAP_SETPCAP": {},
"CAP_NET_BIND_SERVICE": {},
"CAP_SYS_CHROOT": {},
"CAP_KILL": {},
"CAP_AUDIT_WRITE": {},
}
func (c *CapabilitySet) GetAddedCapabilities() []string {
var added []string
for _, cap := range c.ListEffective() {
if _, isDefault := defaultDockerCaps[cap]; !isDefault {
added = append(added, cap)
}
}
return added
}
func (c *CapabilitySet) GetDroppedDefaultCapabilities() []string {
var dropped []string
for cap := range defaultDockerCaps {
if !c.HasCapability(cap) {
dropped = append(dropped, cap)
}
}
return dropped
}
func (c *CapabilitySet) HasOnlyDefaultCapabilities() bool {
for _, cap := range c.ListEffective() {
if _, isDefault := defaultDockerCaps[cap]; !isDefault {
return false
}
}
return true
}