Skip to content

Commit 736a28e

Browse files
committed
Bugfix: accessors should provide their underlying file (Velocidex#2893)
We sometimes need to know the path to the underlying file on the filesystem if possible. This is used in cases when we need to delegate to an external library which expects a filesystem path. Previously the code assumed that when the accessor was "file" or "auto" then the underlying path can be obtained from the filename. This works well in local trigage mode but fails when remapping - in that case the actual accessor called "file" may be a completely different remapped accessor and it is not appropriate to use its filename as an underlying API file. This would cause issues with e.g. the yara plugin, sqlite and leveldb plugins. This PR introduces a new interface which allows the accessor to provide the raw API accessible path if possible. For plugins that need to work with real files, this path also creates a local copy if needed. Fixes: Velocidex#2870
1 parent 4bf3949 commit 736a28e

File tree

13 files changed

+180
-110
lines changed

13 files changed

+180
-110
lines changed

accessors/api.go

+29
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"time"
1010

1111
"github.com/Velocidex/ordereddict"
12+
errors "github.com/go-errors/errors"
1213
"www.velocidex.com/golang/velociraptor/json"
1314
"www.velocidex.com/golang/velociraptor/utils"
1415
"www.velocidex.com/golang/vfilter"
@@ -320,3 +321,31 @@ type FileSystemAccessor interface {
320321
LstatWithOSPath(path *OSPath) (FileInfo, error)
321322
New(scope vfilter.Scope) (FileSystemAccessor, error)
322323
}
324+
325+
// Some filesystems can attempt to retrieve the underlying file. If
326+
// this interface exists on the accessor **and** the
327+
// GetUnderlyingAPIFilename() call succeeds, then it should be
328+
// possible to directly access the returned filename using the OS
329+
// APIs.
330+
type RawFileAPIAccessor interface {
331+
GetUnderlyingAPIFilename(path *OSPath) (string, error)
332+
}
333+
334+
var (
335+
NotRawFileSystem = errors.New("NotRawFileSystem")
336+
)
337+
338+
func GetUnderlyingAPIFilename(accessor string,
339+
scope vfilter.Scope, path *OSPath) (string, error) {
340+
accessor_obj, err := GetAccessor(accessor, scope)
341+
if err != nil {
342+
return "", err
343+
}
344+
345+
raw_accessor, ok := accessor_obj.(RawFileAPIAccessor)
346+
if !ok {
347+
return "", NotRawFileSystem
348+
}
349+
350+
return raw_accessor.GetUnderlyingAPIFilename(path)
351+
}

accessors/file/accessor_common.go

+5
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,11 @@ func (self OSFileSystemAccessor) ReadDir(dir string) ([]accessors.FileInfo, erro
293293
return self.ReadDirWithOSPath(full_path)
294294
}
295295

296+
func (self *OSFileSystemAccessor) GetUnderlyingAPIFilename(
297+
full_path *accessors.OSPath) (string, error) {
298+
return full_path.PathSpec().Path, nil
299+
}
300+
296301
func (self OSFileSystemAccessor) ReadDirWithOSPath(
297302
full_path *accessors.OSPath) ([]accessors.FileInfo, error) {
298303
dir := full_path.PathSpec().Path

accessors/file/auto_windows.go

+5
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,11 @@ func (self AutoFilesystemAccessor) New(scope vfilter.Scope) (accessors.FileSyste
135135
}, nil
136136
}
137137

138+
func (self *AutoFilesystemAccessor) GetUnderlyingAPIFilename(
139+
full_path *accessors.OSPath) (string, error) {
140+
return full_path.PathSpec().Path, nil
141+
}
142+
138143
func (self *AutoFilesystemAccessor) ReadDirWithOSPath(
139144
path *accessors.OSPath) ([]accessors.FileInfo, error) {
140145
result, err := self.file_delegate.ReadDirWithOSPath(path)

accessors/file/os_windows.go

+5
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,11 @@ func (self OSFileSystemAccessor) New(scope vfilter.Scope) (
151151
return result, nil
152152
}
153153

154+
func (self *OSFileSystemAccessor) GetUnderlyingAPIFilename(
155+
full_path *accessors.OSPath) (string, error) {
156+
return full_path.PathSpec().Path, nil
157+
}
158+
154159
func discoverDriveLetters() ([]accessors.FileInfo, error) {
155160
result := []accessors.FileInfo{}
156161

artifacts/testdata/server/testcases/remapping.out.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ LET _ <= remap(config=format(format=RemappingTemplate, args=[ srcDir+'/artifacts
171171
]SELECT * FROM parse_ntfs_i30( accessor='ntfs', device='c:/$MFT', inode="41-144-1")[
172172
{
173173
"MFTId": "45",
174+
"SequenceNumber": 0,
174175
"Mtime": "2018-09-24T07:55:44.4592119Z",
175176
"Atime": "2022-03-18T04:09:07.2885951Z",
176177
"Ctime": "2018-09-24T07:55:20.6489276Z",

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ require (
9797
howett.net/plist v1.0.0
9898
www.velocidex.com/golang/evtx v0.2.1-0.20220404133451-1fdf8be7325e
9999
www.velocidex.com/golang/go-ese v0.1.1-0.20220107095505-c38622559671
100-
www.velocidex.com/golang/go-ntfs v0.1.2-0.20230728152253-4d399c766ed6
100+
www.velocidex.com/golang/go-ntfs v0.1.2-0.20230815140127-6a3dd72bfbf1
101101
www.velocidex.com/golang/go-pe v0.1.1-0.20230228112150-ef2eadf34bc3
102102
www.velocidex.com/golang/go-prefetch v0.0.0-20220801101854-338dbe61982a
103103
www.velocidex.com/golang/oleparse v0.0.0-20230217092320-383a0121aafe

go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -1231,8 +1231,8 @@ www.velocidex.com/golang/evtx v0.2.1-0.20220404133451-1fdf8be7325e h1:AhcXPgNKhJ
12311231
www.velocidex.com/golang/evtx v0.2.1-0.20220404133451-1fdf8be7325e/go.mod h1:ykEQ7AUF9AL+mfCefDmLwmZOnU2So6wM3qKM8xdsHhU=
12321232
www.velocidex.com/golang/go-ese v0.1.1-0.20220107095505-c38622559671 h1:pfvo7NFo0eJj6Zr7d+4vMx/Zr2JriMMPEWRHUf1YjUw=
12331233
www.velocidex.com/golang/go-ese v0.1.1-0.20220107095505-c38622559671/go.mod h1:qnzHyB9yD2khtYO+wf3ck9FQxX3wFhXeJHFBnuUIZcc=
1234-
www.velocidex.com/golang/go-ntfs v0.1.2-0.20230728152253-4d399c766ed6 h1:CQTXpiMZ01PJIvpelSzpWJlZEUoQM831YgHEVdaZic4=
1235-
www.velocidex.com/golang/go-ntfs v0.1.2-0.20230728152253-4d399c766ed6/go.mod h1:itvbHQcnLdTVIDY6fI3lR0zeBwXwBYBdUFtswE0x1vc=
1234+
www.velocidex.com/golang/go-ntfs v0.1.2-0.20230815140127-6a3dd72bfbf1 h1:6NMITYv1pi4tzmDcqB/enNUXKmS8dnTb72HBghqhnAM=
1235+
www.velocidex.com/golang/go-ntfs v0.1.2-0.20230815140127-6a3dd72bfbf1/go.mod h1:itvbHQcnLdTVIDY6fI3lR0zeBwXwBYBdUFtswE0x1vc=
12361236
www.velocidex.com/golang/go-pe v0.1.1-0.20220107093716-e91743c801de/go.mod h1:j9Xy8Z9wxzY2SCB8CqDkkoSzy+eUwevnOrRm/XM2q/A=
12371237
www.velocidex.com/golang/go-pe v0.1.1-0.20230228112150-ef2eadf34bc3 h1:W394TEIFuHFxHY8mzTJPHI5v+M+NLKEHmHn7KY/VpEM=
12381238
www.velocidex.com/golang/go-pe v0.1.1-0.20230228112150-ef2eadf34bc3/go.mod h1:agYwYzeeytVtdwkRrvxZAjgIA8SCeM/Tg7Ym2/jBwmA=

vql/common/yara.go

+26-20
Original file line numberDiff line numberDiff line change
@@ -146,23 +146,32 @@ func (self YaraScanPlugin) Call(
146146
}
147147
matcher.filename = filename
148148

149-
// If accessor is not specified we call yara's
150-
// ScanFile API which mmaps the entire file
151-
// into memory avoiding the need for
152-
// buffering.
153-
if arg.Accessor == "" || arg.Accessor == "file" {
154-
err := matcher.scanFile(ctx, output_chan)
149+
accessor, err := accessors.GetAccessor(arg.Accessor, scope)
150+
if err != nil {
151+
scope.Log("yara: %v", err)
152+
return
153+
}
154+
155+
// As an optimization, we try to call yara's ScanFile API
156+
// which mmaps the entire file into memory avoiding the
157+
// need for buffering.
158+
raw_accessor, ok := accessor.(accessors.RawFileAPIAccessor)
159+
if ok {
160+
underlying_file, err := raw_accessor.GetUnderlyingAPIFilename(filename)
155161
if err == nil {
156-
continue
157-
} else {
158-
scope.Log("Directly scanning file %v failed, will use accessor",
159-
filename.String())
162+
err := matcher.scanFile(ctx, underlying_file, output_chan)
163+
if err == nil {
164+
continue
165+
} else {
166+
scope.Log("Directly scanning file %v failed, will use accessor",
167+
filename.String())
168+
}
160169
}
161170
}
162171

163172
// If scanning with the file api failed above
164173
// we fall back to accessor scanning.
165-
matcher.scanFileByAccessor(ctx, arg.Accessor,
174+
matcher.scanFileByAccessor(ctx, arg.Accessor, accessor,
166175
arg.Blocksize, arg.Start, arg.End, output_chan)
167176
}
168177
}()
@@ -234,18 +243,13 @@ func getYaraRules(key, namespace, rules string,
234243
func (self *scanReporter) scanFileByAccessor(
235244
ctx context.Context,
236245
accessor_name string,
246+
accessor accessors.FileSystemAccessor,
237247
blocksize uint64,
238248
start, end uint64,
239249
output_chan chan vfilter.Row) {
240250

241251
defer utils.CheckForPanic("Panic in scanFileByAccessor")
242252

243-
accessor, err := accessors.GetAccessor(accessor_name, self.scope)
244-
if err != nil {
245-
self.scope.Log("yara: %v", err)
246-
return
247-
}
248-
249253
// Open the file with the accessor
250254
f, err := accessor.OpenWithOSPath(self.filename)
251255
if err != nil {
@@ -354,9 +358,11 @@ func (self *scanReporter) scanRange(start, end uint64, f accessors.ReadSeekClose
354358
// filename to libyara directly for faster scanning using mmap. This
355359
// also ensures that all yara features (like the PE plugin) work.
356360
func (self *scanReporter) scanFile(
357-
ctx context.Context, output_chan chan vfilter.Row) error {
361+
ctx context.Context,
362+
underlying_file string,
363+
output_chan chan vfilter.Row) error {
358364

359-
fd, err := os.Open(self.filename.String())
365+
fd, err := os.Open(underlying_file)
360366
if err != nil {
361367
return err
362368
}
@@ -377,7 +383,7 @@ func (self *scanReporter) scanFile(
377383
err = scanner.SetCallback(self).
378384
SetTimeout(10 * time.Second).
379385
SetFlags(self.yara_flag).
380-
ScanFile(self.filename.String())
386+
ScanFile(underlying_file)
381387
if err != nil {
382388
return err
383389
}

vql/parsers/csv/csv.go

+9-2
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ func (self _WatchCSVPlugin) Info(scope vfilter.Scope, type_map *vfilter.TypeMap)
224224
}
225225

226226
type WriteCSVPluginArgs struct {
227-
Filename string `vfilter:"required,field=filename,doc=CSV files to open"`
227+
Filename *accessors.OSPath `vfilter:"required,field=filename,doc=CSV files to open"`
228228
Accessor string `vfilter:"optional,field=accessor,doc=The accessor to use"`
229229
Query vfilter.StoredQuery `vfilter:"required,field=query,doc=query to write into the file."`
230230
}
@@ -257,7 +257,14 @@ func (self WriteCSVPlugin) Call(
257257
return
258258
}
259259

260-
file, err := os.OpenFile(arg.Filename,
260+
underlying_file, err := accessors.GetUnderlyingAPIFilename(
261+
arg.Accessor, scope, arg.Filename)
262+
if err != nil {
263+
scope.Log("write_csv: %s", err)
264+
return
265+
}
266+
267+
file, err := os.OpenFile(underlying_file,
261268
os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0700)
262269
if err != nil {
263270
scope.Log("write_csv: Unable to open file %s: %s",

vql/parsers/json.go

+9-2
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,7 @@ func (self _IndexAssociativeProtocol) GetMembers(
485485
}
486486

487487
type WriteJSONPluginArgs struct {
488-
Filename string `vfilter:"required,field=filename,doc=CSV files to open"`
488+
Filename *accessors.OSPath `vfilter:"required,field=filename,doc=CSV files to open"`
489489
Accessor string `vfilter:"optional,field=accessor,doc=The accessor to use"`
490490
Query vfilter.StoredQuery `vfilter:"required,field=query,doc=query to write into the file."`
491491
}
@@ -518,7 +518,14 @@ func (self WriteJSONPlugin) Call(
518518
return
519519
}
520520

521-
file, err := os.OpenFile(arg.Filename,
521+
underlying_file, err := accessors.GetUnderlyingAPIFilename(
522+
arg.Accessor, scope, arg.Filename)
523+
if err != nil {
524+
scope.Log("write_csv: %s", err)
525+
return
526+
}
527+
528+
file, err := os.OpenFile(underlying_file,
522529
os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0700)
523530
if err != nil {
524531
scope.Log("write_jsonl: Unable to open file %s: %s",

vql/parsers/leveldb.go

+47-55
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ func (self LevelDBPlugin) Call(
3232
scope vfilter.Scope,
3333
args *ordereddict.Dict) <-chan vfilter.Row {
3434
output_chan := make(chan vfilter.Row)
35+
3536
go func() {
3637
defer close(output_chan)
3738
defer utils.RecoverVQL(scope)
@@ -53,54 +54,9 @@ func (self LevelDBPlugin) Call(
5354
return
5455
}
5556

56-
var db *leveldb.DB
57-
switch arg.Accessor {
58-
case "", "auto", "file":
59-
db, err = leveldb.OpenFile(arg.Filename.String(), &opt.Options{
60-
ReadOnly: true,
61-
Strict: opt.NoStrict,
62-
})
63-
if err != nil {
64-
if !retriableError(err) {
65-
scope.Log("leveldb: %v", err)
66-
return
67-
}
68-
scope.Log("DEBUG:leveldb: Directly opening file faild with %v, retrying on a local copy", err)
69-
local_path, err1 := maybeMakeLocalCopy(ctx, scope, arg)
70-
if err1 != nil {
71-
scope.Log("leveldb: %v", err)
72-
scope.Log("leveldb: %v", err1)
73-
return
74-
}
75-
76-
// Try again with the copy
77-
db, err = leveldb.OpenFile(local_path, &opt.Options{
78-
ReadOnly: true,
79-
Strict: opt.NoStrict,
80-
})
81-
if err != nil {
82-
scope.Log("leveldb: %v", err)
83-
return
84-
}
85-
}
86-
87-
// For other accessors we just always make a copy.
88-
default:
89-
local_path, err := maybeMakeLocalCopy(ctx, scope, arg)
90-
if err != nil {
91-
scope.Log("leveldb: %v", err)
92-
return
93-
}
94-
95-
// Try again with the copy
96-
db, err = leveldb.OpenFile(local_path, &opt.Options{
97-
ReadOnly: true,
98-
Strict: opt.NoStrict,
99-
})
100-
if err != nil {
101-
scope.Log("leveldb: %v", err)
102-
return
103-
}
57+
db, err := getLevelDBHandle(ctx, scope, arg.Accessor, arg.Filename)
58+
if err != nil {
59+
return
10460
}
10561
defer db.Close()
10662

@@ -134,25 +90,61 @@ func (self LevelDBPlugin) Info(scope vfilter.Scope, type_map *vfilter.TypeMap) *
13490
}
13591
}
13692

93+
func getLevelDBHandle(
94+
ctx context.Context, scope vfilter.Scope,
95+
accessor string, filename *accessors.OSPath) (
96+
db *leveldb.DB, err error) {
97+
98+
underlying_file, err := accessors.GetUnderlyingAPIFilename(
99+
accessor, scope, filename)
100+
if err == nil {
101+
// Try to open the underlying_file
102+
db, err = leveldb.OpenFile(underlying_file, &opt.Options{
103+
ReadOnly: true,
104+
Strict: opt.NoStrict,
105+
})
106+
if err == nil {
107+
// Ok it worked, lets use it.
108+
return db, nil
109+
}
110+
scope.Log("DEBUG:leveldb: Directly opening file faild with %v, retrying on a local copy", err)
111+
}
112+
113+
local_path, err := makeLocalCopy(ctx, scope, accessor, filename)
114+
if err != nil {
115+
scope.Log("leveldb: %v", err)
116+
return
117+
}
118+
119+
// Try again with the copy
120+
return leveldb.OpenFile(local_path, &opt.Options{
121+
ReadOnly: true,
122+
Strict: opt.NoStrict,
123+
})
124+
}
125+
137126
// Maybe make a local copy of the database files.
138-
func maybeMakeLocalCopy(
127+
func makeLocalCopy(
139128
ctx context.Context, scope vfilter.Scope,
140-
arg *LevelDBPluginArgs) (string, error) {
141-
accessor, err := accessors.GetAccessor(arg.Accessor, scope)
129+
accessor_name string,
130+
filename *accessors.OSPath) (string, error) {
131+
132+
accessor, err := accessors.GetAccessor(accessor_name, scope)
142133
if err != nil {
143134
return "", err
144135
}
145136

146-
files, err := accessor.ReadDirWithOSPath(arg.Filename)
137+
files, err := accessor.ReadDirWithOSPath(filename)
147138
if err != nil {
148139
return "", err
149140
}
150141

151142
// Create a temp directory to contain all the files.
152-
tmpdir_any := (&filesystem.TempdirFunction{}).Call(ctx, scope, ordereddict.NewDict())
143+
tmpdir_any := (&filesystem.TempdirFunction{}).Call(
144+
ctx, scope, ordereddict.NewDict())
153145
tmpdir, ok := tmpdir_any.(string)
154146
if !ok {
155-
return "", errors.New("Unable to create tempdir")
147+
return "", errors.New("leveldb: Unable to create tempdir")
156148
}
157149

158150
total_bytes := 0
@@ -178,7 +170,7 @@ func maybeMakeLocalCopy(
178170

179171
scope.Log("INFO:leveldb: Copied db %v with accessor %v to local "+
180172
"tmp directory %v (Copied %v files, %v bytes)\n",
181-
arg.Filename, arg.Accessor, tmpdir, len(files), total_bytes)
173+
filename.String(), accessor_name, tmpdir, len(files), total_bytes)
182174
return tmpdir, nil
183175
}
184176

0 commit comments

Comments
 (0)