/
dentry_resolver.go
347 lines (277 loc) · 8.56 KB
/
dentry_resolver.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-present Datadog, Inc.
// +build linux
package probe
import (
"C"
"fmt"
"unsafe"
lib "github.com/DataDog/ebpf"
lru "github.com/hashicorp/golang-lru"
"github.com/pkg/errors"
)
import "github.com/n9e/n9e-agentd/staging/datadog-agent/pkg/security/model"
const (
dentryPathKeyNotFound = "error: dentry path key not found"
fakeInodeMSW = 0xdeadc001
)
// DentryResolver resolves inode/mountID to full paths
type DentryResolver struct {
probe *Probe
pathnames *lib.Map
cache map[uint32]*lru.Cache
}
// ErrInvalidKeyPath is returned when inode or mountid are not valid
type ErrInvalidKeyPath struct {
Inode uint64
MountID uint32
}
func (e *ErrInvalidKeyPath) Error() string {
return fmt.Sprintf("invalid inode/mountID couple: %d/%d", e.Inode, e.MountID)
}
// ErrEntryNotFound is thrown when a path key was not found in the cache
var ErrEntryNotFound = errors.New("entry not found")
// PathKey identifies an entry in the dentry cache
type PathKey struct {
Inode uint64
MountID uint32
PathID uint32
}
func (p *PathKey) Write(buffer []byte) {
model.ByteOrder.PutUint64(buffer[0:8], p.Inode)
model.ByteOrder.PutUint32(buffer[8:12], p.MountID)
model.ByteOrder.PutUint32(buffer[12:16], p.PathID)
}
// IsNull returns true if a key is invalid
func (p *PathKey) IsNull() bool {
return p.Inode == 0 && p.MountID == 0
}
func (p *PathKey) String() string {
return fmt.Sprintf("%x/%x", p.MountID, p.Inode)
}
// MarshalBinary returns the binary representation of a path key
func (p *PathKey) MarshalBinary() ([]byte, error) {
if p.IsNull() {
return nil, &ErrInvalidKeyPath{Inode: p.Inode, MountID: p.MountID}
}
return make([]byte, 16), nil
}
// PathValue describes a value of an entry of the cache
type PathValue struct {
Parent PathKey
Name [model.MaxSegmentLength + 1]byte
}
// DelCacheEntry removes an entry from the cache
func (dr *DentryResolver) DelCacheEntry(mountID uint32, inode uint64) {
if entries, exists := dr.cache[mountID]; exists {
key := PathKey{Inode: inode}
// Delete path recursively
for {
path, exists := entries.Get(key.Inode)
if !exists {
break
}
entries.Remove(key.Inode)
parent := path.(PathValue).Parent
if parent.Inode == 0 {
break
}
// Prepare next key
key = parent
}
}
}
// DelCacheEntries removes all the entries belonging to a mountID
func (dr *DentryResolver) DelCacheEntries(mountID uint32) {
delete(dr.cache, mountID)
}
func (dr *DentryResolver) lookupInode(mountID uint32, inode uint64) (pathValue PathValue, err error) {
entries, exists := dr.cache[mountID]
if !exists {
return pathValue, ErrEntryNotFound
}
entry, exists := entries.Get(inode)
if !exists {
return pathValue, ErrEntryNotFound
}
return entry.(PathValue), nil
}
func (dr *DentryResolver) cacheInode(mountID uint32, inode uint64, pathValue PathValue) error {
entries, exists := dr.cache[mountID]
if !exists {
var err error
entries, err = lru.New(128)
if err != nil {
return err
}
dr.cache[mountID] = entries
}
entries.Add(inode, pathValue)
return nil
}
func (dr *DentryResolver) getNameFromCache(mountID uint32, inode uint64) (name string, err error) {
path, err := dr.lookupInode(mountID, inode)
if err != nil {
return "", err
}
return C.GoString((*C.char)(unsafe.Pointer(&path.Name))), nil
}
func (dr *DentryResolver) getNameFromMap(mountID uint32, inode uint64, pathID uint32) (name string, err error) {
key := PathKey{MountID: mountID, Inode: inode, PathID: pathID}
var path PathValue
if err := dr.pathnames.Lookup(key, &path); err != nil {
return "", fmt.Errorf("unable to get filename for mountID `%d` and inode `%d`", mountID, inode)
}
return C.GoString((*C.char)(unsafe.Pointer(&path.Name))), nil
}
// GetName resolves a couple of mountID/inode to a path
func (dr *DentryResolver) GetName(mountID uint32, inode uint64, pathID uint32) string {
name, err := dr.getNameFromCache(mountID, inode)
if err != nil {
name, _ = dr.getNameFromMap(mountID, inode, pathID)
}
return name
}
// ResolveFromCache resolve from the cache
func (dr *DentryResolver) ResolveFromCache(mountID uint32, inode uint64) (filename string, err error) {
key := PathKey{MountID: mountID, Inode: inode}
// Fetch path recursively
for {
path, err := dr.lookupInode(key.MountID, key.Inode)
if err != nil {
return "", err
}
// Don't append dentry name if this is the root dentry (i.d. name == '/')
if path.Name[0] != '\x00' && path.Name[0] != '/' {
filename = "/" + C.GoString((*C.char)(unsafe.Pointer(&path.Name))) + filename
}
if path.Parent.Inode == 0 {
break
}
// Prepare next key
key = path.Parent
}
if len(filename) == 0 {
filename = "/"
}
return
}
// ResolveFromMap resolves from kernel map
func (dr *DentryResolver) ResolveFromMap(mountID uint32, inode uint64, pathID uint32) (string, error) {
key := PathKey{MountID: mountID, Inode: inode, PathID: pathID}
var path PathValue
var filename, segment string
var err, resolutionErr error
keyBuffer, err := key.MarshalBinary()
if err != nil {
return "", err
}
toAdd := make(map[PathKey]PathValue)
// Fetch path recursively
for {
key.Write(keyBuffer)
if err = dr.pathnames.Lookup(keyBuffer, &path); err != nil {
filename = dentryPathKeyNotFound
break
}
cacheKey := PathKey{MountID: key.MountID, Inode: key.Inode}
toAdd[cacheKey] = path
if path.Name[0] == '\x00' {
resolutionErr = errTruncatedParents
break
}
// Don't append dentry name if this is the root dentry (i.d. name == '/')
if path.Name[0] != '/' {
segment = C.GoString((*C.char)(unsafe.Pointer(&path.Name)))
if len(segment) >= (model.MaxSegmentLength) {
resolutionErr = errTruncatedSegment
}
filename = "/" + segment + filename
}
if path.Parent.Inode == 0 {
break
}
// Prepare next key
key = path.Parent
}
// resolution errors are more important than regular map lookup errors
if resolutionErr != nil {
err = resolutionErr
}
if len(filename) == 0 {
filename = "/"
}
if err == nil {
for k, v := range toAdd {
// do not cache fake path keys in the case of rename events
if k.Inode>>32 != fakeInodeMSW {
_ = dr.cacheInode(k.MountID, k.Inode, v)
}
}
}
return filename, err
}
// Resolve the pathname of a dentry, starting at the pathnameKey in the pathnames table
func (dr *DentryResolver) Resolve(mountID uint32, inode uint64, pathID uint32) (string, error) {
path, err := dr.ResolveFromCache(mountID, inode)
if err != nil {
path, err = dr.ResolveFromMap(mountID, inode, pathID)
}
return path, err
}
func (dr *DentryResolver) getParentFromCache(mountID uint32, inode uint64) (uint32, uint64, error) {
path, err := dr.lookupInode(mountID, inode)
if err != nil {
return 0, 0, ErrEntryNotFound
}
return path.Parent.MountID, path.Parent.Inode, nil
}
func (dr *DentryResolver) getParentFromMap(mountID uint32, inode uint64, pathID uint32) (uint32, uint64, error) {
key := PathKey{MountID: mountID, Inode: inode, PathID: pathID}
var path PathValue
if err := dr.pathnames.Lookup(key, &path); err != nil {
return 0, 0, err
}
return path.Parent.MountID, path.Parent.Inode, nil
}
// GetParent - Return the parent mount_id/inode
func (dr *DentryResolver) GetParent(mountID uint32, inode uint64, pathID uint32) (uint32, uint64, error) {
parentMountID, parentInode, err := dr.getParentFromCache(mountID, inode)
if err != nil {
parentMountID, parentInode, err = dr.getParentFromMap(mountID, inode, pathID)
}
return parentMountID, parentInode, err
}
// Start the dentry resolver
func (dr *DentryResolver) Start() error {
pathnames, ok, err := dr.probe.manager.GetMap("pathnames")
if err != nil {
return err
}
if !ok {
return errors.New("map pathnames not found")
}
dr.pathnames = pathnames
return nil
}
// ErrTruncatedSegment is used to notify that a segment of the path was truncated because it was too long
type ErrTruncatedSegment struct{}
func (err ErrTruncatedSegment) Error() string {
return "truncated_segment"
}
var errTruncatedSegment ErrTruncatedSegment
// ErrTruncatedParents is used to notify that some parents of the path are missing
type ErrTruncatedParents struct{}
func (err ErrTruncatedParents) Error() string {
return "truncated_parents"
}
var errTruncatedParents ErrTruncatedParents
// NewDentryResolver returns a new dentry resolver
func NewDentryResolver(probe *Probe) (*DentryResolver, error) {
return &DentryResolver{
probe: probe,
cache: make(map[uint32]*lru.Cache),
}, nil
}