Skip to content

Commit

Permalink
Bugfix: Make filestore accessor case insensitive. (Velocidex#3645)
Browse files Browse the repository at this point in the history
The filestore accessor is most commonly used in remapping for
postprocessing (for example

https://docs.velociraptor.app/blog/2022/2022-08-04-post-processing/#automating-the-remapping).
This usually comes from Windows systems and so it assumes case
insensitivity.

This PR enables case insensitivity by default.
  • Loading branch information
scudette authored Jul 26, 2024
1 parent c887cd6 commit f646ebc
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 3 deletions.
32 changes: 29 additions & 3 deletions accessors/file_store/accessor.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,15 @@ func (self FileStoreFileSystemAccessor) LstatWithOSPath(filename *accessors.OSPa
fullpath := path_specs.FromGenericComponentList(filename.Components)
lstat, err := self.file_store.StatFile(fullpath)
if err != nil {
return nil, err
// If it didnt work, we try case insensitive open
corrected_path, err := getCorrectCase(self.file_store, fullpath)
if err != nil {
return nil, err
}
lstat, err = self.file_store.StatFile(corrected_path)
if err != nil {
return nil, err
}
}

return file_store_file_info.NewFileStoreFileInfoWithOSPath(
Expand Down Expand Up @@ -87,7 +95,16 @@ func (self FileStoreFileSystemAccessor) ReadDirWithOSPath(
fullpath := path_specs.FromGenericComponentList(filename.Components)
files, err := self.file_store.ListDirectory(fullpath)
if err != nil {
return nil, err
// If it didnt work, we try case insensitive
corrected_path, err := getCorrectCase(self.file_store, fullpath)
if err != nil {
return nil, err
}

files, err = self.file_store.ListDirectory(corrected_path)
if err != nil {
return nil, err
}
}

var result []accessors.FileInfo
Expand Down Expand Up @@ -142,7 +159,16 @@ func (self FileStoreFileSystemAccessor) OpenWithOSPath(filename *accessors.OSPat
}

if err != nil {
return nil, err
// If it didnt work, we try case insensitive open
corrected_path, err := getCorrectCase(self.file_store, fullpath)
if err != nil {
return nil, err
}

file, err = self.file_store.ReadFile(corrected_path)
if err != nil {
return nil, err
}
}
}

Expand Down
97 changes: 97 additions & 0 deletions accessors/file_store/accessor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package file_store

import (
"errors"
"testing"

"github.com/stretchr/testify/suite"
config_proto "www.velocidex.com/golang/velociraptor/config/proto"
"www.velocidex.com/golang/velociraptor/file_store"
"www.velocidex.com/golang/velociraptor/file_store/directory"
"www.velocidex.com/golang/velociraptor/file_store/path_specs"
"www.velocidex.com/golang/velociraptor/file_store/test_utils"
"www.velocidex.com/golang/velociraptor/utils"
"www.velocidex.com/golang/velociraptor/vtesting/assert"
)

type testCase struct {
checked string
err error
}

var (
files = []string{
"fs:/Windows/System32/notepad.exe",
"fs:/Windows/System32/NotePad2.exe",

// Filestores allow data to be stored in a "directory"
"fs:/Windows/System32",
}

checked_files = []testCase{
{checked: "fs:/WinDowS/SySteM32/NotePad.exe"},
{checked: "fs:/Windows/System32/NotePad2.exe"},

// Filestores allow data to be stored in a "directory"
{checked: "fs:/windows/system32"},

{checked: "fs:/Windows/System32/DoesNotExist.exe",
err: utils.NotFoundError},
}
)

type FSAccessorTest struct {
test_utils.TestSuite

config_obj *config_proto.Config
file_store *directory.DirectoryFileStore
}

func (self *FSAccessorTest) TestCaseInsensitive() {
accessor := NewFileStoreFileSystemAccessor(self.ConfigObj)

file_store_factory := file_store.GetFileStore(self.ConfigObj)

buf := make([]byte, 100)

// Create some files with data
for _, f := range files {
pathspec, err := accessor.ParsePath(f)
assert.NoError(self.T(), err)

fullpath := path_specs.FromGenericComponentList(pathspec.Components)
w, err := file_store_factory.WriteFile(fullpath)
assert.NoError(self.T(), err)
w.Write([]byte("hello"))
w.Close()

// Use the accessor to open a file directly.
fd, err := accessor.Open(f)
assert.NoError(self.T(), err)

n, err := fd.Read(buf)
assert.NoError(self.T(), err)
assert.Equal(self.T(), n, 5)
assert.Equal(self.T(), string(buf[:n]), "hello")
}

for _, testcase := range checked_files {
// Now open the same file with the wrong casing.
fd, err := accessor.Open(testcase.checked)
if testcase.err != nil {
assert.True(self.T(), errors.Is(err, testcase.err))
continue
}
assert.NoError(self.T(), err)

n, err := fd.Read(buf)
assert.NoError(self.T(), err)
assert.Equal(self.T(), n, 5)
assert.Equal(self.T(), string(buf[:n]), "hello")
}

}

func TestFileStoreAccessor(t *testing.T) {
suite.Run(t, &FSAccessorTest{})
}
52 changes: 52 additions & 0 deletions accessors/file_store/casing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package file_store

import (
"strings"

"www.velocidex.com/golang/velociraptor/file_store/api"
"www.velocidex.com/golang/velociraptor/utils"
)

// Correct the filename to its correct casing.
func getCorrectCase(
file_store api.FileStore,
filename api.FSPathSpec) (api.FSPathSpec, error) {

// File is exactly fine.
_, err := file_store.StatFile(filename)
if err == nil {
return filename, nil
}

// File is not found, try to find the correct casing for the base
// component.
basename := filename.Base()
dirname := filename.Dir()

// For non root directories we need to look at the parents
if len(dirname.Components()) > 0 {
_, err := file_store.StatFile(dirname)
if err != nil {
// The parent directory can not be directly opened - it is
// possible that the parent directory casing is incorrect too.
dirname, err = getCorrectCase(file_store, dirname)
if err != nil {
return nil, err
}
}
}

// Try again with the correct case. It should work this time.
entries, err := file_store.ListDirectory(dirname)
if err != nil {
return nil, err
}

for _, e := range entries {
if strings.EqualFold(e.Name(), basename) {
return dirname.AddChild(e.Name()), nil
}
}

return nil, utils.NotFoundError
}

0 comments on commit f646ebc

Please sign in to comment.