Skip to content

Remove fslib and upgrade go-ntfs. Implement low level read ourselves#49763

Open
brian-mckinney wants to merge 3 commits intoelastic:mainfrom
brian-mckinney:remove_fslib_dep
Open

Remove fslib and upgrade go-ntfs. Implement low level read ourselves#49763
brian-mckinney wants to merge 3 commits intoelastic:mainfrom
brian-mckinney:remove_fslib_dep

Conversation

@brian-mckinney
Copy link
Copy Markdown
Contributor

@brian-mckinney brian-mckinney commented Mar 29, 2026

Proposed commit message

Removes the dependency on the fslib library. fslib is pinned to a much older version of go-ntfs, and if we kept the fslib dependency, we could not use the newer version of go-ntfs for the upcoming MFT table.

fslib was used for a single purpose, to perform low level forensic read of a system locked file (the Amcache registry hive). With a bit of help from Claude to identify how it worked, I was able to pretty easily extract the functionality we were using and implement it ourselves with go-ntfs.

Summary:

  • Removes fslib dependency
  • Upgrades go-ntfs dependency to v0.2.1
  • Adds a low level file read function to replace fslib
  • Updates registry.go to use the new function when reading the amcache hive

Checklist

  • My code follows the style guidelines of this project
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • I have made corresponding change to the default configuration files
  • I have added tests that prove my fix is effective or that my feature works. Where relevant, I have used the stresstest.sh script to run them under stress conditions and race detector to verify their stability.
  • I have added an entry in ./changelog/fragments using the changelog tool.

@brian-mckinney brian-mckinney self-assigned this Mar 29, 2026
@brian-mckinney brian-mckinney requested review from a team as code owners March 29, 2026 16:53
@botelastic botelastic bot added the needs_team Indicates that the issue/PR needs a Team:* label label Mar 29, 2026
@github-actions
Copy link
Copy Markdown
Contributor

🤖 GitHub comments

Just comment with:

  • run docs-build : Re-trigger the docs validation. (use unformatted text in the comment!)

@mergify
Copy link
Copy Markdown
Contributor

mergify bot commented Mar 29, 2026

This pull request does not have a backport label.
If this is a bug or security fix, could you label this PR @brian-mckinney? 🙏.
For such, you'll need to label your PR with:

  • The upcoming major version of the Elastic Stack
  • The upcoming minor version of the Elastic Stack (if you're not pushing a breaking change)

To fixup this pull request, you need to add the backport labels for the needed
branches, such as:

  • backport-8./d is the label to automatically backport to the 8./d branch. /d is the digit
  • backport-active-all is the label that automatically backports to all active branches.
  • backport-active-8 is the label that automatically backports to all active minor branches for the 8 major.
  • backport-active-9 is the label that automatically backports to all active minor branches for the 9 major.

@brian-mckinney brian-mckinney added the Team:Security-Windows Platform Windows Platform Team in Security Solution label Mar 29, 2026
@botelastic botelastic bot removed the needs_team Indicates that the issue/PR needs a Team:* label label Mar 29, 2026
@elasticmachine
Copy link
Copy Markdown
Contributor

Pinging @elastic/sec-windows-platform (Team:Security-Windows Platform)

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 29, 2026

📝 Walkthrough

Walkthrough

This PR removes the fslib dependency and replaces its NTFS read logic with go-ntfs. go.mod is updated to add go-ntfs and related Velocidex modules and drop fslib. The amcache registry code replaces the fslib-based fallback with readFileViaNTFS, which opens the raw volume (\.<drive>:), parses NTFS structures, locates files via the MFT, and reads the $DATA attribute. A Windows-only test calls readFileViaNTFS against C:\Windows\AppCompat\Programs\Amcache.hve and validates the returned registry data.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • 🛠️ Update Documentation: Commit on current branch
  • 🛠️ Update Documentation: Create PR

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@x-pack/osquerybeat/ext/osquery-extension/pkg/amcache/registry/registry_test.go`:
- Around line 57-70: The test Test_readFileViaNTFS should be adjusted to match
the current readFileViaNTFS signature and to avoid panics and CI failures: call
readFileViaNTFS with the single parameter expected by the implementation (use
readFileViaNTFS(amcachePath) and remove the logger argument or update to the
function signature if you prefer changing the implementation), handle the
returned error by skipping the test when the error indicates lack of
raw-volume/permission (use t.Skipf with the error) instead of failing the run,
and guard any slice access by checking len(data) >= len(magic) before using
data[:len(magic)] (or skip/assert with a clear message) — reference
Test_readFileViaNTFS, readFileViaNTFS, magic, and data to locate the affected
code.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 695a93aa-94dd-451a-971e-52f373a364e3

📥 Commits

Reviewing files that changed from the base of the PR and between 2d10f57 and 9637945.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (4)
  • changelog/fragments/1774802490-remove_fslib_dep.yaml
  • go.mod
  • x-pack/osquerybeat/ext/osquery-extension/pkg/amcache/registry/registry.go
  • x-pack/osquerybeat/ext/osquery-extension/pkg/amcache/registry/registry_test.go

Comment on lines +57 to +70
func Test_readFileViaNTFS(t *testing.T) {
log := logger.New(os.Stdout, true)

// Read the actual amcache hive using the readFileViaNTFS function to ensure it can read the file and return valid registry data.
amcachePath := "C:\\Windows\\AppCompat\\Programs\\Amcache.hve"

// Read the file using
data, err := readFileViaNTFS(amcachePath, log)
assert.NoError(t, err, "readFileViaNTFS() failed: %v", err)
assert.NotEmpty(t, data, "readFileViaNTFS() returned empty data")

magic := []byte{0x72, 0x65, 0x67, 0x66} // "regf"
log.Infof("readFileViaNTFS() returned data with magic: %x", data[:5])
assert.True(t, bytes.HasPrefix(data, magic), "readFileViaNTFS() returned data with incorrect magic")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n -C2 '\bfunc readFileViaNTFS\s*\(|\breadFileViaNTFS\s*\(|data\[:5\]|Test_readFileViaNTFS|\\\\\.\\C:' \
  x-pack/osquerybeat/ext/osquery-extension/pkg/amcache/registry/registry.go \
  x-pack/osquerybeat/ext/osquery-extension/pkg/amcache/registry/registry_test.go

Repository: elastic/beats

Length of output: 3137


Fix compile error and panic risk in Test_readFileViaNTFS before merge.

Line 64 calls readFileViaNTFS(amcachePath, log) with two arguments, but the function is declared with one parameter in registry.go (compile failure). Line 69 accesses data[:5] without bounds checking, causing panic on short reads. The test also hard-fails on runners without raw-volume access, blocking CI.

Proposed fix
 import (
 	"bytes"
+	"errors"
 	"os"
 	"testing"
@@
-	data, err := readFileViaNTFS(amcachePath, log)
+	data, err := readFileViaNTFS(amcachePath)
+	if err != nil && (errors.Is(err, os.ErrPermission) || errors.Is(err, os.ErrNotExist)) {
+		t.Skipf("skipping NTFS raw-read test on this runner: %v", err)
+	}
 	assert.NoError(t, err, "readFileViaNTFS() failed: %v", err)
 	assert.NotEmpty(t, data, "readFileViaNTFS() returned empty data")
@@
 	magic := []byte{0x72, 0x65, 0x67, 0x66} // "regf"
-	log.Infof("readFileViaNTFS() returned data with magic: %x", data[:5])
+	assert.GreaterOrEqual(t, len(data), len(magic), "readFileViaNTFS() returned truncated data")
+	log.Infof("readFileViaNTFS() returned data with magic: %x", data[:len(magic)])
 	assert.True(t, bytes.HasPrefix(data, magic), "readFileViaNTFS() returned data with incorrect magic")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@x-pack/osquerybeat/ext/osquery-extension/pkg/amcache/registry/registry_test.go`
around lines 57 - 70, The test Test_readFileViaNTFS should be adjusted to match
the current readFileViaNTFS signature and to avoid panics and CI failures: call
readFileViaNTFS with the single parameter expected by the implementation (use
readFileViaNTFS(amcachePath) and remove the logger argument or update to the
function signature if you prefer changing the implementation), handle the
returned error by skipping the test when the error indicates lack of
raw-volume/permission (use t.Skipf with the error) instead of failing the run,
and guard any slice access by checking len(data) >= len(magic) before using
data[:len(magic)] (or skip/assert with a clear message) — reference
Test_readFileViaNTFS, readFileViaNTFS, magic, and data to locate the affected
code.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
x-pack/osquerybeat/ext/osquery-extension/pkg/amcache/registry/registry_test.go (1)

10-12: ⚠️ Potential issue | 🔴 Critical

Make this raw-volume test fail cleanly on unsupported runners.

It still assumes a C: Amcache hive plus raw NTFS access, so it can fail on ordinary Windows runners. More importantly, read/parse failures still flow into data[:5] and registry.OpenKey(...), which can panic instead of skip/fail cleanly.

Possible fix
 import (
 	"bytes"
+	"errors"
 	"os"
 	"testing"
@@
 	// Read the file using
 	data, err := readFileViaNTFS(amcachePath)
-	assert.NoError(t, err, "readFileViaNTFS() failed: %v", err)
-	assert.NotEmpty(t, data, "readFileViaNTFS() returned empty data")
+	if err != nil {
+		if errors.Is(err, os.ErrPermission) || errors.Is(err, os.ErrNotExist) {
+			t.Skipf("skipping NTFS raw-read test on this runner: %v", err)
+		}
+		t.Fatalf("readFileViaNTFS() failed: %v", err)
+	}
+	if len(data) == 0 {
+		t.Fatal("readFileViaNTFS() returned empty data")
+	}
 
 	magic := []byte{0x72, 0x65, 0x67, 0x66} // "regf"
-	log.Infof("readFileViaNTFS() returned data with magic: %x", data[:5])
-	assert.True(t, bytes.HasPrefix(data, magic), "readFileViaNTFS() returned data with incorrect magic")
+	if len(data) < len(magic) {
+		t.Fatalf("readFileViaNTFS() returned truncated data: %d bytes", len(data))
+	}
+	log.Infof("readFileViaNTFS() returned data with magic: %x", data[:len(magic)])
+	if !bytes.HasPrefix(data, magic) {
+		t.Fatal("readFileViaNTFS() returned data with incorrect magic")
+	}
 
 	registry, err := regparser.NewRegistry(bytes.NewReader(data))
-	assert.NoError(t, err, "failed to create registry from NTFS data")
-	assert.NotNil(t, registry, "registry is nil")
+	if err != nil {
+		t.Fatalf("failed to create registry from NTFS data: %v", err)
+	}
+	if registry == nil {
+		t.Fatal("registry is nil")
+	}
 
 	keyNode := registry.OpenKey("Root\\InventoryApplication")
-	assert.NotNil(t, keyNode, "failed to open key Root\\InventoryApplication")
+	if keyNode == nil {
+		t.Fatal("failed to open key Root\\InventoryApplication")
+	}
 }

Also applies to: 57-77

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@x-pack/osquerybeat/ext/osquery-extension/pkg/amcache/registry/registry_test.go`
around lines 10 - 12, The raw-volume test must be made to skip cleanly on
unsupported runners and avoid panics on read/parse failures: at the start of the
test (e.g., TestRawVolume or similarly named test) detect unsupported
environments (non-Windows, missing C: volume, or lack of raw NTFS access) and
call t.Skipf with an explanatory message; before taking data[:5] or calling
registry.OpenKey ensure the file read/parse succeeded (check for nil/len bounds
and error return values) and return or t.Fatalf/t.Skip on error instead of
slicing or calling registry.OpenKey on invalid data; update any helper that
opens the hive to return an error which the test checks, and reference the
registry.OpenKey call and the data variable so the guards are added immediately
before those operations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In
`@x-pack/osquerybeat/ext/osquery-extension/pkg/amcache/registry/registry_test.go`:
- Around line 10-12: The raw-volume test must be made to skip cleanly on
unsupported runners and avoid panics on read/parse failures: at the start of the
test (e.g., TestRawVolume or similarly named test) detect unsupported
environments (non-Windows, missing C: volume, or lack of raw NTFS access) and
call t.Skipf with an explanatory message; before taking data[:5] or calling
registry.OpenKey ensure the file read/parse succeeded (check for nil/len bounds
and error return values) and return or t.Fatalf/t.Skip on error instead of
slicing or calling registry.OpenKey on invalid data; update any helper that
opens the hive to return an error which the test checks, and reference the
registry.OpenKey call and the data variable so the guards are added immediately
before those operations.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7440aa5c-a047-43b6-a5d7-9350965a3b2a

📥 Commits

Reviewing files that changed from the base of the PR and between 9637945 and 3680629.

📒 Files selected for processing (2)
  • x-pack/osquerybeat/ext/osquery-extension/pkg/amcache/registry/registry.go
  • x-pack/osquerybeat/ext/osquery-extension/pkg/amcache/registry/registry_test.go
✅ Files skipped from review due to trivial changes (1)
  • x-pack/osquerybeat/ext/osquery-extension/pkg/amcache/registry/registry.go

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Osquerybeat Team:Security-Windows Platform Windows Platform Team in Security Solution

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants