github.com/DataDog/datadog-agent/pkg/security/secl@v0.55.0-devel.0.20240517055856-10c4965fea94/model/model_helpers_unix.go (about) 1 // Unless explicitly stated otherwise all files in this repository are licensed 2 // under the Apache License Version 2.0. 3 // This product includes software developed at Datadog (https://www.datadoghq.com/). 4 // Copyright 2016-present Datadog, Inc. 5 6 //go:build unix 7 8 package model 9 10 import ( 11 "encoding/binary" 12 "errors" 13 "fmt" 14 "path" 15 "path/filepath" 16 "strings" 17 "syscall" 18 "time" 19 20 "github.com/DataDog/datadog-agent/pkg/security/secl/compiler/eval" 21 ) 22 23 const ( 24 OverlayFS = "overlay" // OverlayFS overlay filesystem 25 TmpFS = "tmpfs" // TmpFS tmpfs 26 UnknownFS = "unknown" // UnknownFS unknown filesystem 27 28 ErrPathMustBeAbsolute = "all the path have to be absolute" // ErrPathMustBeAbsolute tells when a path is not absolute 29 ErrPathDepthLimit = "path depths have to be shorter than" // ErrPathDepthLimit tells when a path is too long 30 ErrPathSegmentLimit = "each segment of a path must be shorter than" // ErrPathSegmentLimit tells when a patch reached the segment limit 31 32 // SizeOfCookie size of cookie 33 SizeOfCookie = 8 34 ) 35 36 // check that all path are absolute 37 func validatePath(field eval.Field, fieldValue eval.FieldValue) error { 38 // do not support regular expression on path, currently unable to support discarder for regex value 39 if fieldValue.Type == eval.RegexpValueType { 40 return fmt.Errorf("regexp not supported on path `%s`", field) 41 } else if fieldValue.Type == eval.VariableValueType { 42 return nil 43 } 44 45 if value, ok := fieldValue.Value.(string); ok { 46 errAbs := fmt.Errorf("invalid path `%s`, %s", value, ErrPathMustBeAbsolute) 47 errDepth := fmt.Errorf("invalid path `%s`, %s %d", value, ErrPathDepthLimit, MaxPathDepth) 48 errSegment := fmt.Errorf("invalid path `%s`, %s %d", value, ErrPathSegmentLimit, MaxSegmentLength) 49 50 if value == "" { 51 return nil 52 } 53 54 if value != path.Clean(value) { 55 return errAbs 56 } 57 58 if value == "*" { 59 return errAbs 60 } 61 62 if !filepath.IsAbs(value) && len(value) > 0 && value[0] != '*' { 63 return errAbs 64 } 65 66 if strings.HasPrefix(value, "~") { 67 return errAbs 68 } 69 70 // check resolution limitations 71 segments := strings.Split(value, "/") 72 if len(segments) > MaxPathDepth { 73 return errDepth 74 } 75 for _, segment := range segments { 76 if segment == ".." { 77 return errAbs 78 } 79 if len(segment) > MaxSegmentLength { 80 return errSegment 81 } 82 } 83 } 84 85 return nil 86 } 87 88 // ValidateField validates the value of a field 89 func (m *Model) ValidateField(field eval.Field, fieldValue eval.FieldValue) error { 90 if strings.HasSuffix(field, "path") { 91 if err := validatePath(field, fieldValue); err != nil { 92 return err 93 } 94 } 95 96 switch field { 97 98 case "event.retval": 99 if value := fieldValue.Value; value != -int(syscall.EPERM) && value != -int(syscall.EACCES) { 100 return errors.New("return value can only be tested against EPERM or EACCES") 101 } 102 case "bpf.map.name", "bpf.prog.name": 103 if value, ok := fieldValue.Value.(string); ok { 104 if len(value) > MaxBpfObjName { 105 return fmt.Errorf("the name provided in %s must be at most %d characters, len(\"%s\") = %d", field, MaxBpfObjName, value, len(value)) 106 } 107 } 108 } 109 110 if m.ExtraValidateFieldFnc != nil { 111 return m.ExtraValidateFieldFnc(field, fieldValue) 112 } 113 114 return nil 115 } 116 117 // SetPathResolutionError sets the Event.pathResolutionError 118 func (ev *Event) SetPathResolutionError(fileFields *FileEvent, err error) { 119 fileFields.PathResolutionError = err 120 ev.Error = err 121 } 122 123 // Equals returns if both credentials are equal 124 func (c *Credentials) Equals(o *Credentials) bool { 125 return c.UID == o.UID && 126 c.GID == o.GID && 127 c.EUID == o.EUID && 128 c.EGID == o.EGID && 129 c.FSUID == o.FSUID && 130 c.FSGID == o.FSGID && 131 c.CapEffective == o.CapEffective && 132 c.CapPermitted == o.CapPermitted 133 } 134 135 // SetSpan sets the span 136 func (p *Process) SetSpan(spanID uint64, traceID uint64) { 137 p.SpanID = spanID 138 p.TraceID = traceID 139 } 140 141 // GetPathResolutionError returns the path resolution error as a string if there is one 142 func (p *Process) GetPathResolutionError() string { 143 return p.FileEvent.GetPathResolutionError() 144 } 145 146 // HasInterpreter returns whether the process uses an interpreter 147 func (p *Process) HasInterpreter() bool { 148 return p.LinuxBinprm.FileEvent.Inode != 0 149 } 150 151 // IsNotKworker returns true if the process isn't a kworker 152 func (p *Process) IsNotKworker() bool { 153 return !p.IsKworker 154 } 155 156 // GetProcessArgv returns the unscrubbed args of the event as an array. Use with caution. 157 func (p *Process) GetProcessArgv() ([]string, bool) { 158 if p.ArgsEntry == nil { 159 return p.Argv, p.ArgsTruncated 160 } 161 162 argv := p.ArgsEntry.Values 163 if len(argv) > 0 { 164 argv = argv[1:] 165 } 166 p.Argv = argv 167 p.ArgsTruncated = p.ArgsTruncated || p.ArgsEntry.Truncated 168 return p.Argv, p.ArgsTruncated 169 } 170 171 // GetProcessArgv0 returns the first arg of the event and whether the process arguments are truncated 172 func (p *Process) GetProcessArgv0() (string, bool) { 173 if p.ArgsEntry == nil { 174 return p.Argv0, p.ArgsTruncated 175 } 176 177 argv := p.ArgsEntry.Values 178 if len(argv) > 0 { 179 p.Argv0 = argv[0] 180 } 181 p.ArgsTruncated = p.ArgsTruncated || p.ArgsEntry.Truncated 182 return p.Argv0, p.ArgsTruncated 183 } 184 185 // Equals compares two FileFields 186 func (f *FileFields) Equals(o *FileFields) bool { 187 return f.Inode == o.Inode && f.MountID == o.MountID && f.MTime == o.MTime && f.UID == o.UID && f.GID == o.GID && f.Mode == o.Mode 188 } 189 190 // IsFileless return whether it is a file less access 191 func (f *FileFields) IsFileless() bool { 192 // TODO(safchain) fix this heuristic by add a flag in the event intead of using mount ID 0 193 return f.Inode != 0 && f.MountID == 0 194 } 195 196 // HasHardLinks returns whether the file has hardlink 197 func (f *FileFields) HasHardLinks() bool { 198 return f.NLink > 1 199 } 200 201 // GetInLowerLayer returns whether a file is in a lower layer 202 func (f *FileFields) GetInLowerLayer() bool { 203 return f.Flags&LowerLayer != 0 204 } 205 206 // GetInUpperLayer returns whether a file is in the upper layer 207 func (f *FileFields) GetInUpperLayer() bool { 208 return f.Flags&UpperLayer != 0 209 } 210 211 // Equals compare two FileEvent 212 func (e *FileEvent) Equals(o *FileEvent) bool { 213 return e.FileFields.Equals(&o.FileFields) 214 } 215 216 // SetPathnameStr set and mark as resolved 217 func (e *FileEvent) SetPathnameStr(str string) { 218 e.PathnameStr = str 219 e.IsPathnameStrResolved = true 220 } 221 222 // SetBasenameStr set and mark as resolved 223 func (e *FileEvent) SetBasenameStr(str string) { 224 e.BasenameStr = str 225 e.IsBasenameStrResolved = true 226 } 227 228 // GetPathResolutionError returns the path resolution error as a string if there is one 229 func (e *FileEvent) GetPathResolutionError() string { 230 if e.PathResolutionError != nil { 231 return e.PathResolutionError.Error() 232 } 233 return "" 234 } 235 236 // IsOverlayFS returns whether it is an overlay fs 237 func (e *FileEvent) IsOverlayFS() bool { 238 return e.Filesystem == "overlay" 239 } 240 241 // GetFSType returns the filesystem type of the mountpoint 242 func (m *Mount) GetFSType() string { 243 return m.FSType 244 } 245 246 // IsOverlayFS returns whether it is an overlay fs 247 func (m *Mount) IsOverlayFS() bool { 248 return m.GetFSType() == "overlay" 249 } 250 251 const ( 252 ProcessCacheEntryFromUnknown = iota // ProcessCacheEntryFromUnknown defines a process cache entry from unknown 253 ProcessCacheEntryFromPlaceholder // ProcessCacheEntryFromPlaceholder defines the source of a placeholder process cache entry 254 ProcessCacheEntryFromEvent // ProcessCacheEntryFromEvent defines a process cache entry from event 255 ProcessCacheEntryFromKernelMap // ProcessCacheEntryFromKernelMap defines a process cache entry from kernel map 256 ProcessCacheEntryFromProcFS // ProcessCacheEntryFromProcFS defines a process cache entry from procfs. Note that some exec parent may be missing. 257 ProcessCacheEntryFromSnapshot // ProcessCacheEntryFromSnapshot defines a process cache entry from snapshot 258 ) 259 260 // ProcessSources defines process sources 261 var ProcessSources = [...]string{ 262 "unknown", 263 "placeholder", 264 "event", 265 "map", 266 "procfs_fallback", 267 "procfs_snapshot", 268 } 269 270 // ProcessSourceToString returns the string corresponding to a process source 271 func ProcessSourceToString(source uint64) string { 272 return ProcessSources[source] 273 } 274 275 // SetTimeout updates the timeout of an activity dump 276 func (adlc *ActivityDumpLoadConfig) SetTimeout(duration time.Duration) { 277 adlc.Timeout = duration 278 adlc.EndTimestampRaw = adlc.StartTimestampRaw + uint64(duration) 279 } 280 281 // GetKey returns a key to uniquely identify a network device on the system 282 func (d NetDevice) GetKey() string { 283 return fmt.Sprintf("%v_%v", d.IfIndex, d.NetNS) 284 } 285 286 func (p *PathKey) Write(buffer []byte) { 287 binary.NativeEndian.PutUint64(buffer[0:8], p.Inode) 288 binary.NativeEndian.PutUint32(buffer[8:12], p.MountID) 289 binary.NativeEndian.PutUint32(buffer[12:16], p.PathID) 290 } 291 292 // IsNull returns true if a key is invalid 293 func (p *PathKey) IsNull() bool { 294 return p.Inode == 0 && p.MountID == 0 295 } 296 297 func (p *PathKey) String() string { 298 return fmt.Sprintf("%x/%x", p.MountID, p.Inode) 299 } 300 301 // MarshalBinary returns the binary representation of a path key 302 func (p *PathKey) MarshalBinary() ([]byte, error) { 303 if p.IsNull() { 304 return nil, &ErrInvalidKeyPath{Inode: p.Inode, MountID: p.MountID} 305 } 306 307 buff := make([]byte, 16) 308 p.Write(buff) 309 310 return buff, nil 311 } 312 313 // PathKeySize defines the path key size 314 const PathKeySize = 16 315 316 // PathLeafSize defines path_leaf struct size 317 const PathLeafSize = PathKeySize + MaxSegmentLength + 1 + 2 + 6 // path_key + name + len + padding 318 319 // PathLeaf is the go representation of the eBPF path_leaf_t structure 320 type PathLeaf struct { 321 Parent PathKey 322 Name [MaxSegmentLength + 1]byte 323 Len uint16 324 Padding [6]uint8 325 } 326 327 // GetName returns the path value as a string 328 func (pl *PathLeaf) GetName() string { 329 return NullTerminatedString(pl.Name[:]) 330 } 331 332 // SetName sets the path name 333 func (pl *PathLeaf) SetName(name string) { 334 copy(pl.Name[:], []byte(name)) 335 pl.Len = uint16(len(name) + 1) 336 } 337 338 // MarshalBinary returns the binary representation of a path key 339 func (pl *PathLeaf) MarshalBinary() ([]byte, error) { 340 buff := make([]byte, PathLeafSize) 341 342 pl.Parent.Write(buff) 343 copy(buff[16:], pl.Name[:]) 344 binary.NativeEndian.PutUint16(buff[16+len(pl.Name):], pl.Len) 345 346 return buff, nil 347 } 348 349 // ResolveHashes resolves the hash of the provided file 350 func (dfh *FakeFieldHandlers) ResolveHashes(_ EventType, _ *Process, _ *FileEvent) []string { 351 return nil 352 } 353 354 // ResolveUserSessionContext resolves and updates the provided user session context 355 func (dfh *FakeFieldHandlers) ResolveUserSessionContext(_ *UserSessionContext) {} 356 357 // SELinuxEventKind represents the event kind for SELinux events 358 type SELinuxEventKind uint32 359 360 const ( 361 // SELinuxBoolChangeEventKind represents SELinux boolean change events 362 SELinuxBoolChangeEventKind SELinuxEventKind = iota 363 // SELinuxStatusChangeEventKind represents SELinux status change events 364 SELinuxStatusChangeEventKind 365 // SELinuxBoolCommitEventKind represents SELinux boolean commit events 366 SELinuxBoolCommitEventKind 367 ) 368 369 // ExtraFieldHandlers handlers not hold by any field 370 type ExtraFieldHandlers interface { 371 BaseExtraFieldHandlers 372 ResolveHashes(eventType EventType, process *Process, file *FileEvent) []string 373 ResolveUserSessionContext(evtCtx *UserSessionContext) 374 }