Bitcoin Core 钱包路径安全收紧:为什么含..与.的相对路径钱包名不再被允许
【免费下载链接】bitcoinBitcoin Core integration/staging tree项目地址: https://gitcode.com/GitHub_Trending/bi/bitcoin
Bitcoin Core 在近期版本中收紧了钱包命名规则:包含..和.元素的相对路径钱包名,以及名为/的钱包不再被允许。本文基于仓库中的发布说明 doc/release-notes-34544.md,结合 钱包路径解析实现 与 多钱包功能测试,完整讲清这一行为变更的规则边界、底层校验逻辑、触发该变更的安全动机,以及依赖旧行为的用户的迁移方案。
变更内容:官方发布说明
该变更记录在 doc/release-notes-34544.md 的 Wallet 小节中,原文要点如下:
Wallets names that are relative paths including
..and.elements, and wallets named/are no longer allowed. Any users that depended on this behavior can instead use absolute paths to their wallet or move their wallet to a safer path.
翻译为具体规则:
- 相对路径中含
..或.元素的钱包名被禁止,例如bad/./path、bad/../path、../wallets/wallet; - 以
.或..开头的相对路径被禁止,例如./、../、./wallet; - 名为
/(文件系统根目录)的钱包名被禁止。
官方给出的迁移建议是两类:对不在默认钱包目录内的钱包,改用绝对路径引用;或将钱包迁移到更安全的目录下。
底层实现:GetWalletPath的三层校验
所有创建/加载钱包的路径入口最终都会经过 GetWalletPath 函数。它接收钱包名name,按以下顺序执行三层校验:
util::Result<fs::path> GetWalletPath(const std::string& name) { const fs::path name_path = fs::PathFromString(name); // 'name' must be a normalized path, i.e. no . or .. except at the root if (name_path != name_path.lexically_normal()) { return util::Error{Untranslated("Wallet name given as a path must be normalized")}; } // 'name' cannot begin with ./ or ../ if (!name_path.empty() && (*name_path.begin() == fs::PathFromString(".") || *name_path.begin() == fs::PathFromString(".."))) { return util::Error{Untranslated("Wallet name given as a relative path cannot begin with ./ or ../, for wallets not in the walletdir, please use an absolute path.")}; } // Disallow path at root if (name_path.has_root_path() && name_path.root_path() == name_path) { return util::Error{Untranslated("Wallet name cannot be the root path")}; } // ... }三层校验各自的含义:
第一层:路径必须已规范化(normalized)
lexically_normal()是 C++17 文件系统库中对路径做字典序标准化的方法,它会折叠.段、消解x/../y这类冗余。函数要求name_path与其规范化结果完全一致,也就是说路径字符串本身不能携带任何.或..段(文件系统根下的合法结构除外)。
这一层的设计意图值得注意:它不是简单地把路径"洗干净"再使用,而是拒绝未规范化的输入。因为规范化会隐藏调用方实际请求的路径与程序最终使用的路径之间的差异,这种差异正是路径穿越(path traversal)类漏洞的温床。拒绝未规范化输入,让"请求的路径 = 解析的路径"成为不变量。
对应错误信息:Wallet name given as a path must be normalized。
第二层:相对路径不能以./或../开头
即使一个路径"看起来"是规范化的,如果它从.或..段开始,也会被单独拒绝。从源码结构看,这一层是第一层的防御纵深:../单独作为整个路径时,其规范化结果仍是..(无法再消解),可能被第一层放行,因此需要显式拦截。错误信息中也明确给出了替代方案提示:
Wallet name given as a relative path cannot begin with ./ or ../, for wallets not in the walletdir, please use an absolute path.
即:如果你的钱包不在默认钱包目录(-walletdir)中,请改用绝对路径。
第三层:禁止根路径/
name_path.has_root_path() && name_path.root_path() == name_path判断路径是否恰好就是文件系统根(如/,在 Windows 上还包括C:\)。禁止以根目录作为"钱包路径",避免在文件系统根目录下创建或写入钱包数据。
校验通过后的合法形态
三层校验通过后,GetWalletPath 继续检查解析出的绝对路径(fsbridge::AbsPathJoin(GetWalletDir(), name_path)拼接 钱包目录)必须满足以下四种形态之一,否则返回Invalid -wallet path错误:
- 一个可以创建目录的位置;
- 一个已存在的目录;
- 一个指向目录的符号链接;
- 向后兼容:
-walletdir下一个已存在数据文件的文件名。
也就是说,普通相对路径仍然完全合法——mywallet、sub/dir/wallet这类不含./..段的名称都会相对 GetWalletDir() 解析。被禁止的只是含.、..段或以根路径形式出现的名字。
变更覆盖的入口:-wallet启动参数与 RPC
这条命名规则并非只约束某一个入口,而是所有"按名字/路径解析钱包"的公共入口,因为 MakeWalletDatabase 在创建钱包数据库前统一调用GetWalletPath:
std::unique_ptr<WalletDatabase> MakeWalletDatabase(const std::string& name, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error_string) { const auto& wallet_path = GetWalletPath(name); if (!wallet_path) { error_string = util::ErrorString(wallet_path); status = DatabaseStatus::FAILED_BAD_PATH; return nullptr; } return MakeDatabase(*wallet_path, options, status, error_string); }因此以下使用方式都受同一规则约束:
- 启动参数:
-wallet=<path>,其定义为"路径指向包含钱包数据与日志文件的目录;若路径不是绝对路径,则相对<walletdir>解释;只能加载已存在的钱包,不会创建新钱包"(见 src/wallet/init.cpp)。这意味着启动时通过-wallet=../outside/wallet之类的方式引用数据目录外部的钱包,现在会直接失败; - RPC:
createwallet、loadwallet等按钱包名操作钱包的调用,同样经过该校验链。
测试证据:功能测试中的非法钱包名清单
test/functional/wallet_multiwallet.py 中的test_invalid_wallet_names用例,完整覆盖了这条规则的三类非法输入与预期错误,是验证该行为最直接的仓库内依据:
def test_invalid_wallet_names(self): self.log.info("Test weird paths are not allowed as wallet names") NON_NORMALIZED = ["bad/./path", "bad/../path", "/bad/./path", "/bad/../path", "../", "./", "./wallet", "../wallets/../wallets/wallet"] for name in NON_NORMALIZED: assert_raises_rpc_error(-4, "Wallet name given as a path must be normalized", self.nodes[0].createwallet, name) INVALID_RELPATH = ["../wallets/wallet", "..", "."] for name in INVALID_RELPATH: assert_raises_rpc_error(-4, "Wallet name given as a relative path cannot begin with ./ or ../", self.nodes[0].createwallet, name) INVALID_ROOT = ["/"] if platform.system() == "Windows": INVALID_ROOT.extend(["C:\\", "C:"]) for name in INVALID_ROOT: assert_raises_rpc_error(-4, "Wallet name cannot be the root path", self.nodes[0].createwallet, name)从该测试可以读出三条规则边界:
| 输入类别 | 示例 | 触发错误 |
|---|---|---|
含./..段的未规范化路径(无论相对还是绝对) | bad/./path、/bad/../path、./wallet | Wallet name given as a path must be normalized |
以./..开头的相对路径 | ../wallets/wallet、..、. | Wallet name given as a relative path cannot begin with ./ or ../ |
| 文件系统根路径 | /(Windows 下含C:\、C:) | Wallet name cannot be the root path |
注意一个细节:./和../被归入"未规范化"一类(第一层拦截),而..、.单独作为完整路径时第一层的比较结果不变,由第二层兜底拦截。这与上面 GetWalletPath 的两段判断顺序完全对应。
为什么收紧:路径解析的安全考量
发布说明本身未展开动机,但从源码结构可以推断其安全意图。相对路径与..段的组合会引入两类风险:
- 路径穿越:
../段允许钱包数据库(LevelDB 数据、日志、备份文件)被写入或读取到-walletdir之外。若不同来源(配置文件、RPC 调用、启动参数)拼装的相对路径在各自工作目录下解释,..段的实际落点会变得难以审计。 - 符号链接与规范化差异:先接受任意路径再规范化,会使"用户请求的位置"与"程序实际使用的位置"分叉。对以目录为单位存放数据库的钱包机制而言,任何落点分叉都可能让备份、卸载(
unloadwallet会清理空目录,见 src/wallet/wallet.cpp)等后续操作作用到与预期不同的目录。
"拒绝未规范化输入 + 拒绝./、../前缀 + 拒绝根路径"这三段组合,把合法钱包名的空间收敛为钱包目录内无歧义的子路径,或显式的绝对路径,落点完全可预测。这也正是发布说明建议"改用绝对路径"的原因:绝对路径天然不含相对解析歧义,且不受当前工作目录影响。
迁移建议:依赖旧行为的用户怎么办
发布说明给出的两条替代路径,对应两类典型场景:
场景一:把钱包放在数据目录之外
如果你之前依赖-wallet=../mywallet或类似写法把钱包数据放在-datadir之外的位置,现在的正确做法是使用绝对路径:
- 启动参数:
bitcoind -wallet=/absolute/path/to/walletdir(参数说明见 src/wallet/init.cpp); - 需要程序化加载时:
loadwallet "/absolute/path/to/walletdir"。
绝对路径不含相对段,能通过全部三层校验,并且落点不受进程工作目录影响,语义上与旧的相对写法等价但无歧义。
场景二:使用含.段的"怪"名字
如果你之前以./wallet、bad/../path之类名字创建过钱包(在旧版本中会被规范化后落在某个目录),现在应当:
- 确认该名字实际解析到的真实目录(即其规范化形态对应的目录);
- 将钱包数据目录移动到期望位置(如
-walletdir下的普通子目录); - 用新名字/新路径加载。钱包数据库目录是 LevelDB 数据与日志的容器,整体移动目录即可,这正是发布说明中"move their wallet to a safer path"的含义。
未受影响的用法
普通钱包名(如mainwallet、cold-storage)、无.段的相对子路径(如backups/cold),以及指向现有目录或数据文件的绝对路径,均不在此次变更影响范围内,可继续使用。
小结
本次变更的核心是一条明确的命名规则:钱包路径名必须已规范化、不得以./或../开头、不得是文件系统根路径,其实现集中在 GetWalletPath,经由 MakeWalletDatabase 统一约束-wallet启动参数与钱包相关 RPC,并由 test/functional/wallet_multiwallet.py 的三类非法输入用例锁定行为。对使用者而言,规则之外的一切照常工作;规则之内的问题用法,官方给出的标准解法只有两个——绝对路径,或把钱包移到钱包目录下的安全位置。
【免费下载链接】bitcoinBitcoin Core integration/staging tree项目地址: https://gitcode.com/GitHub_Trending/bi/bitcoin
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考