#1591·fiber

`fnn --restore` fails to overwrite an existing read-only Fiber private key file

Author: sunchengzhuCreated Jul 29, 2026Updated Aug 4, 2026

English | 中文

Summary

After PR #1197 introduced online backup and restore, including Data Loss Protection (DLP), a normally initialized FNN node stores <fiber_base_dir>/sk with read-only 0o400 permissions. When fnn --restore is run by a non-root user, it attempts to overwrite this file with std::fs::copy, which fails with Permission denied (os error 13) and exits.

The failure occurs before the database checkpoint is restored and before eligible channels are scanned and marked Stale, so the rest of the recovery flow never runs.

In plain terms: FNN protects sk by making it read-only, but fnn --restore later tries to overwrite that same file as if it were writable. A non-root process must honor the read-only permission, so restore stops before the database and channel state can be recovered; running as root may hide the problem.

Environment

  • Fiber source: develop @ 5db52a2486220222e8905beb3f17ee3ef35a9307, fnn Fiber v0.9.0-rc7 (5db52a2 2026-07-28)
  • System and storage: macOS 26.3, Apple Silicon (arm64), RocksDB
  • Execution user: UID 501 (non-root). A root process that can bypass normal file permission checks does not fail because of 0o400; therefore, containers or CI environments that run FNN as root may not reproduce the issue.

Discovery and reproduction

This issue was found by the backup/restore black-box regression tests for PR #1197.

  1. Start FNN and let it create <fiber_base_dir>/sk; the private key file has permissions 0o400.
  2. Call the Admin RPC backup method to create a complete backup containing db/, sk, and key.
  3. Stop the node, leave the existing key files unchanged, and restore the backup into the same node data directory:
bash
FIBER_SECRET_KEY_PASSWORD='password0' fnn -c <config.yml> -d <base_dir> \
  --restore <fiber_base_dir>/backups/<timestamp>

Observed:

INFO fnn: Starting manual restore process from: "<backup_path>"
Error: Exit because Failed to restore database: Database error:
Failed to restore fiber key: Permission denied (os error 13)

When the destination already contains an sk file with 0o400 permissions, all three in-place recovery scenarios fail at this step: restoring a backup of the current channel state, making a payment and then restoring an older backup, and relying on peer reconnection after restore to reestablish channel state.

As a control, after deleting the destination key and sk, the same backup successfully restores both key files and preserves the Fiber node public key. This confirms that the backup is valid and that the failure is caused by overwriting the existing read-only sk.

Expected: fnn --restore completes without requiring the operator to run chmod or delete key files first, and the restored sk retains owner-read-only 0o400 permissions.

Root cause

On Unix, FNN sets the Fiber private key permissions to 0o400: fiber/key.rs#L40-L58.

The restore flow then uses std::fs::copy to overwrite the existing sk: store/restore.rs#L35-L50. This operation must open and truncate the destination file with write access, so a non-root process cannot overwrite an sk with 0o400 permissions.

create sk (0o400) -> restore_node_keys() -> std::fs::copy() -> EACCES
                     -> DB restore / Stale scan never runs

As a diagnostic step, changing <fiber_base_dir>/sk to 0o600 immediately before running fnn --restore makes all three in-place recovery scenarios pass. Database checkpoint restoration, Stale marking, peer reconnection, and the post-restore payment flow then work as expected. This was only used to isolate the cause and should not be required as a user-side workaround.

The earlier implementation copied key files to store_path instead of the actual <fiber_base_dir>/sk, so it did not trigger this failure. After f82c085 switched the restore target to fiber_key_path, the restore flow began attempting to overwrite the read-only sk.

A different Permission denied error was previously reported in the discussion on PR #1197: repeated backups attempted to overwrite a read-only sk inside the backup directory. This issue occurs when restore overwrites the read-only sk in the active node data directory, so the trigger and impact are different.

Impact

  • When FNN runs as a non-root user and the destination sk already exists, the standard fnn --restore flow cannot complete.
  • Restore exits while processing the Fiber private key, before the database is restored or channels are scanned and marked Stale.
  • Backup creation is unaffected. Manual backups, startup backups, new checkpoints triggered by channel-state changes, and the generated key and sk backup files have all been verified successfully.

Suggested fix

  • Write the restored keys to temporary files in the same destination directory, validate their contents, and apply restrictive permissions. Then replace the existing key files in one filesystem operation (for example, rename/replace) to avoid writing directly to read-only files or leaving partially written keys.
  • Before replacing any data, verify that sk, key, and the database checkpoint all exist and are readable. On failure, preserve the original keys and database to avoid a partial restore.
  • The restored Fiber sk should retain 0o400 permissions. The implementation should also account for the different handling of read-only files on Unix and Windows.

Why this is not a duplicate of Issue #1585

Issue #1585 concerns overly permissive permissions on newly created RocksDB files. This issue concerns fnn --restore failing to overwrite an existing read-only sk, so the trigger and root cause are different. PR #1589 tightens the process umask but does not change the overwrite behavior in restore_node_keys, so it does not fix this issue.


中文版

fnn --restore 无法覆盖只读的 Fiber 私钥文件

问题概述

PR #1197 引入在线备份、恢复和数据丢失保护(Data Loss Protection,DLP)后,正常启动的 FNN 会将 <fiber_base_dir>/sk 的权限设置为只读的 0o400。当 FNN 以非 root 身份运行时,fnn --restore 使用 std::fs::copy 直接覆盖该文件会返回 Permission denied (os error 13) 并退出。

该错误发生在数据库检查点(checkpoint)恢复以及相关通道被扫描并标记为 Stale 之前,因此后续恢复流程无法执行。

通俗地说:FNN 为保护私钥而把 sk 设为只读,但 fnn --restore 随后又把它当成可写文件直接覆盖。非 root 进程必须遵守只读权限,因此恢复会在数据库和通道状态处理之前中断;以 root 身份运行则可能掩盖这个问题。

复现环境

  • Fiber:develop @ 5db52a2486220222e8905beb3f17ee3ef35a9307fnn Fiber v0.9.0-rc7 (5db52a2 2026-07-28)
  • 环境:macOS 26.3、Apple Silicon(arm64)、RocksDB
  • 运行用户:UID 501(非 root)。若 root 进程具备绕过普通文件权限检查的能力,则不会因 0o400 触发该错误;因此,以 root 身份运行的容器或 CI 环境可能无法复现。

发现方式与复现场景

该问题由针对 PR #1197 的 backup/restore 黑盒回归测试 发现。

  1. 启动 FNN,使其生成 <fiber_base_dir>/sk;该私钥文件的权限为 0o400
  2. 调用 Admin RPC 的 backup 方法,生成包含 db/skkey 的完整备份。
  3. 停止节点,保留现有密钥文件不变,在同一个节点数据目录中执行:
bash
FIBER_SECRET_KEY_PASSWORD='password0' fnn -c <config.yml> -d <base_dir> \
  --restore <fiber_base_dir>/backups/<timestamp>

实际结果:

INFO fnn: Starting manual restore process from: "<backup_path>"
Error: Exit because Failed to restore database: Database error:
Failed to restore fiber key: Permission denied (os error 13)

目标目录中已存在权限为 0o400sk 时,三种原地恢复场景都会在此处失败:恢复当前通道备份、支付后恢复旧备份,以及恢复后通过对端重连重新建立通道状态。作为对照,删除目标 keysk 后,同一备份可以成功恢复两个密钥文件,且 Fiber 节点公钥(pubkey)保持不变。这说明备份本身有效,失败是由覆盖现有只读 sk 触发的。预期行为是:fnn --restore 无需操作员预先执行 chmod 或删除密钥文件即可完成恢复,且恢复后的 sk 仍保持仅所有者可读的 0o400 权限。

原因分析

FNN 在 Unix 平台将 Fiber 私钥设置为 0o400fiber/key.rs#L40-L58

恢复流程随后使用 std::fs::copy 直接覆盖现有 skstore/restore.rs#L35-L50。对于非 root 进程,该操作需要以可写方式打开并截断目标文件,因此无法覆盖权限为 0o400sk

create sk (0o400) -> restore_node_keys() -> std::fs::copy() -> EACCES
                     -> DB restore / Stale scan 未执行

作为定位手段,在执行 fnn --restore 前临时运行 chmod 600 <fiber_base_dir>/sk。这样处理后,上述三个原地恢复场景全部通过,说明数据库检查点恢复、Stale 标记、对端重连以及恢复后的支付流程均正常。这只是一种定位手段,不应作为用户侧解决方案。

早期实现会把密钥复制到 store_path,而不是实际使用的 <fiber_base_dir>/sk,因此未触发本问题。f82c085 改为使用 fiber_key_path 后,fnn --restore 才会尝试覆盖这份只读 sk

PR #1197 的讨论中曾报告过另一个 Permission denied:重复备份时,程序会覆盖备份目录中的只读 sk;本问题则发生在 fnn --restore 覆盖节点数据目录中的只读 sk 时,触发位置和影响均不同。

影响

  • 当 FNN 以非 root 身份运行且目标 sk 已存在时,标准 fnn --restore 无法完成。
  • fnn --restore 在处理 Fiber 私钥文件时退出,数据库恢复和通道 Stale 扫描不会执行。
  • 备份生成不受影响;手动备份、启动时备份、通道状态变化触发的新 checkpoint,以及 keysk 备份产物均已验证成功。

建议修复方向

  • 先将恢复的密钥写入目标目录中的临时文件,校验内容并设置严格权限,再通过一次性文件替换操作覆盖现有密钥(例如 rename/replace),避免直接写入只读文件或留下不完整的密钥。
  • 替换前验证 skkey 和数据库检查点均存在且可读;失败时保留原密钥与原数据库,避免部分恢复。
  • 恢复后的 Fiber sk 应保持 0o400;实现时还需兼容 Unix 和 Windows 对只读文件的不同处理方式。

Issue #1585 的区别

Issue #1585 描述的是新生成的 RocksDB 文件权限过宽;本问题则是 fnn --restore 无法覆盖现有的只读 sk,两者的触发位置和根因不同。PR #1589 设置了 umask,但没有改变 restore_node_keys 的覆盖逻辑,因此不能修复本问题。