Repository navigation
Refuse shared storage directories and don't follow symlinks - #1179
ajaysehwal wants to merge 1 commit into
Conversation
|
I've assigned @tnull as a reviewer! |
a4c34f6 to
6f661e4
Compare
By default we store all node data, including the `ChannelMonitor`s, under `/tmp/ldk_node`. If that directory had been pre-created by another local user, we'd happily use it (`create_dir_all_private` accepts existing directories as-is), allowing them to read, replace or roll back our state, or to redirect our log or database file via a symlink. Here, we add `create_dir_all_private_checked`, which on Unix additionally refuses to use an existing directory if it is owned by a user other than us or `root`, or if it is writable by all users. We use it for the configured storage directory when building the SQLite- and fs-store-based `Node`, and for the default log directory. Further, we open the default log file (in the storage directory) with `O_NOFOLLOW` and refuse to open a symlinked SQLite database file. Explicitly configured log file paths still follow symlinks, as before. We also document that the default location is only suitable for testing and add `set_storage_dir_path` to the README/crate-level example. This change was prepared with the assistance of AI tools (Claude Code).
6f661e4 to
e15d123
Compare
tnull
left a comment
There was a problem hiding this comment.
I don't think this is worth it - users are responsible to handle access to the data folder. We can't fully protect them from messing up, and may also not have the full picture.
Understood, that’s fair. Users own their data directory, and we can’t see the full picture of how it’s deployed, so I’ll drop the ownership and permission checks and the symlink handling. The one piece I’d still suggest is a short docs note that the default /tmp/ldk_node is only meant for testing, with the README and crate-level example setting a storage directory explicitly. It adds no behavior change. If you’d rather not have that either, I’m happy to close this. |
By default we store all node data, including the
ChannelMonitors, under/tmp/ldk_node.create_dir_all_privateaccepts an existing directory without checking who owns it or who can write to it. If another local user pre-creates it (e.g.,mkdir -m 0777 /tmp/ldk_node) before our first start, they can read our state, replace or roll back the database, or plant a symlink (e.g.,ldk_node.log -> ~/.bashrc) so we append log lines to a file of their choosing.This hardens how we handle the storage directory:
create_dir_all_private_checked, which on Unix refuses an existing directory that is owned by a user other than the current user orroot, or that is world-writable (mode & 0o002). It's used for the configuredstorage_dir_path(SQLite and fs-store) and the default log directory. Failures surface asBuildError::StoragePathAccessFailed, with the cause logged.O_NOFOLLOW, and refuse a SQLite database file that is a symlink. Explicitly configured log paths still follow symlinks, as before.Notes:
fsGroup) and0755directories from older versions are still accepted. World-writable directories, or ones owned by another non-root user, are now rejected. The main case this could break is a root-owned0777directory used by a non-root process, such as a KubernetesemptyDir. I can relax the world-writable check to apply only when we don't own the directory if that's a concern./tmp/ldk_node. Moving it or refusing it on mainnet would be an API change, so I left that for a separate discussion.O_NOFOLLOWapplies only to the default log file, since users may legitimately symlink their own log file, and failing there would hit the logger'sexpecton every write.unsafe { libc::geteuid() }call (it can't fail). No new dependencies:libcwas already a direct dependency.Tests: new unit tests cover directory checks (world-writable rejected, group-writable and own directories accepted), a symlinked SQLite database, a symlinked default log file (rejected) vs. a configured one (accepted), and
Builder::buildrefusing a world-writable directory.cargo test --libpasses, andcargo check --libcompiles withuniffiand withstorage-sqlitedisabled. I didn't run the integration tests.AI disclosure: prepared with the assistance of Claude Code.