Skip to content

receiver, compactor, sidecar: use os.Root API#8797

Merged
GiedriusS merged 5 commits into
thanos-io:mainfrom
guidonguido:fix/os-root-api
Jun 3, 2026
Merged

receiver, compactor, sidecar: use os.Root API#8797
GiedriusS merged 5 commits into
thanos-io:mainfrom
guidonguido:fix/os-root-api

Conversation

@guidonguido

@guidonguido guidonguido commented Apr 30, 2026

Copy link
Copy Markdown
Contributor
  • I added CHANGELOG entry for this change.
  • Change is not relevant to the end user.

Fixes #8103

Issue

Current os filesystem access methods do not prevent accidental path traversal access, so directory operations based on user input may access unintended paths.

Changes

Force the use of os.Root to confine access to subdirectories only to the root working folder of the specific service.

Approach

Receiver, Sidecar: long-lived os.Root. The data directory persists for the entire service lifetime, so the Root is stored in the owning struct.
Compactor: transient os.Root. The compaction dir is fully removed after each compaction iteration, so the Root dir is opened on-demand.

Analysis

Receiver: real protection from user input, since the tenants directory paths are retrieved from the tenant IDs, derived either from HTTP Header, TLS cert or metric label.

Compactor: no path is composed from user input. The change only prevents accidental use on the codebase side.

Sidecar: as compactor.

@guidonguido
guidonguido force-pushed the fix/os-root-api branch 3 times, most recently from a662f3e to 866be3e Compare April 30, 2026 10:05
Comment thread cmd/thanos/receive.go
return errors.Wrapf(err, "create data dir in %v", conf.dataDir)
}

dataDir, err := os.OpenRoot(conf.dataDir)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why not have this in createDefaultTenantTSDB itself?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

dataDir is opened once and used in two places: createDefaultTenantTSDB for the default tenant dir and then handed to the long-living MultiTSDB that uses it for all filesystem interactions.
The alternative I see is to create a Root short-lived object for every dataDir operation; if that's not the intention, a dataDir is created once and used repetedly

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

My understanding of the os.Root API is that is is intended to be used as a long-lived, concurrency-safe file descriptor, so you don't have to check the dir validity after the initial open

Comment thread cmd/thanos/rule.go
}
}()

dataDir, err := os.OpenRoot(conf.dataDir)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same here, why not create this in shipper itself?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Similar to before, even if the root object is only consumed by the shipper: if the intention is to have a long-living Root reference, it is better to create it at the caller level. Moving it to the shipper would mean to change the New(...) func signature to handle the error or recreate the Root when needed.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

But if the old conf.dataDir is passed then we wouldn't have to make any changes to shipper.New() because it would accept the same params, no?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

If we want to have a "reusable" os.Root, we would need to change Shipper.dir from string to os.Root; doing so in the New() func would require the function to return an error.

If we keep Shipper.dir a path string as it is, we would need to instantiate an os.Root on the fly when needed e.g. blockMetasFromOldest() and upload(), which breaks the long-lived os.Root object I thought we wanted to implement. Not a big issue, just wanted to clarify pros and cons of the two approaches

@ogulcanaydogan

Copy link
Copy Markdown
Contributor

The TODO on the MkdirAll in runReceive, os.OpenRoot would fail if the dir doesn't exist so the MkdirAll is needed. The old early-return in createDefautTenantTSDB that bailed when dataDir was missing is now gone, so this is the only place handling that case. Makes sense to me.

@GiedriusS

Copy link
Copy Markdown
Member

@guidonguido are you still interested in continuing this PR?

@guidonguido

Copy link
Copy Markdown
Contributor Author

Hello @GiedriusS. Yes, I missed the review notifications. I'll follow-up the review in the following days. Sorry for the delay

@guidonguido
guidonguido force-pushed the fix/os-root-api branch 2 times, most recently from db1927b to 6733bf8 Compare May 21, 2026 12:48
Comment thread cmd/thanos/receive.go Outdated
Comment thread cmd/thanos/rule.go Outdated
Comment thread cmd/thanos/rule.go
}
}()

dataDir, err := os.OpenRoot(conf.dataDir)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

But if the old conf.dataDir is passed then we wouldn't have to make any changes to shipper.New() because it would accept the same params, no?

Comment thread pkg/compact/compact.go Outdated
@guidonguido

Copy link
Copy Markdown
Contributor Author

Edited the minor changes using the logging and error utilities for close operations.

The discussion on where to instantiate the os.Root object(s) is still open

@GiedriusS GiedriusS left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think let's just merge this as is because it doesn't make much of a difference in the end. And, of course, to avoid bikeshedding. If we ever feel in the future that we need to abstract it away in the shipper then we can do that change. Adding a few comments. Let's always use the helpers with closers.

Comment thread cmd/thanos/rule.go Outdated
Comment thread cmd/thanos/sidecar.go Outdated
Signed-off-by: Guido Ricioppo <[email protected]>

receive e2e: close mtsdb datadir on object release

Signed-off-by: Guido Ricioppo <[email protected]>

Fix tests after rebase

Signed-off-by: Guido Ricioppo <[email protected]>
Signed-off-by: Guido Ricioppo <[email protected]>

Refactor compactor os.Root lifetime

Signed-off-by: Guido Ricioppo <[email protected]>
Signed-off-by: Guido Ricioppo <[email protected]>
Signed-off-by: Guido Ricioppo <[email protected]>

Fix doc error

Signed-off-by: Guido Ricioppo <[email protected]>
@guidonguido

Copy link
Copy Markdown
Contributor Author

@GiedriusS checked all FDs are closed now using helpers: using CloseWithLogOnErr instead of CloseWithErrCapture in case no named return exists

@GiedriusS GiedriusS left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice cleanup! 🚀

@GiedriusS
GiedriusS merged commit b372e39 into thanos-io:main Jun 3, 2026
22 of 23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Use the new os.Root type

4 participants