rustdesk / rustdesk/rustdesk-server

hbbs writes client-style config file to data directory via Docker bind mount

オープン
#653 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

主要言語
Rust
スター
10.4k
フォーク
2.6k
PR マージ指標
30日以内にマージされた PR はありません

説明

Summary

When hbbs (RustDesk rendezvous server) starts inside a Docker container with a bind-mounted data directory (./data:/root), it inadvertently creates a ~/.config/rustdesk/RustDesk.toml file inside the data directory. This file is the same client-side config format used by RustDesk desktop/mobile clients and should not exist in the server's working directory.

The file is created by hbb_common (a shared library used by both client and server), specifically through Config::get_key_pair() being called at startup via a fallback path in get_uuid().


Affected Component

  • Binary: hbbs (rendezvous server)
  • Not affected: hbbr (relay server) — does not use hbb_common::Config startup chain that triggers config write
  • Library: libs/hbb_common in both rustdesk-server and rustdesk repos

Environment

  • Deployment: Docker with volumes: ./data:/root (bind mount)
  • Host path affected: /root/docker/rustdesk/data/.config/rustdesk/RustDesk.toml
  • Permissions: 0o600 (owned by root)
  • Container user: root

Root Cause Analysis

Trigger chain

The entry point is check_software_update() in the hbbs main thread, which spawns a worker thread. That worker calls version_check_request(), which invokes get_fingerprint() — and FingerprintingInfo::new() accesses Config::get_id(), which triggers the CONFIG lazy_static initializer (and thus Config::load()).

hbbs main()                                                    [src/main.rs:10]
  ├─ init_args()                                                [src/common.rs:56]
  ├─ check_software_update()                                    [src/common.rs:193]
  │    └─ thread: check_software_update_()                      [src/common.rs:201]
  │         └─ version_check_request(...)                       [hbb_common/src/lib.rs:425]
  │              └─ fingerprint::get_fingerprint(None, None)     [hbb_common/src/lib.rs:434]
  │                   └─ FingerprintingInfo::new()              [hbb_common/src/fingerprint.rs:198]
  │                        └─ Config::get_id()                  [hbb_common/src/fingerprint.rs:203]
  │                             └─ CONFIG.read()                TRIGGERS lazy_static!
  │                                  └─ Config::load()          [hbb_common/src/config.rs:53]
  │                                       ├─ decrypt_str_or_original(password)
  │                                       ├─ decrypt_str_or_original(enc_id)  
  │                                       ├─ get_auto_id()     [MAC succeeds in Docker]
  │                                       │    └─ config.id = "auto_id_str"
  │                                       │    └─ store = true
  │                                       └─ config.store()    [hbb_common/src/config.rs:573]
  │                                            ├─ encrypt_str_or_original(config.id)
  │                                            │    └─ encrypt()
  │                                            │         └─ symmetric_crypt()
  │                                            │              └─ get_uuid()
  │                                            │                   └─ Config::get_key_pair().1
  │                                            │                        ├─ Config::load_<Config>("")   [line 893]
  │                                            │                        ├─ key_pair empty → gen_keypair()
  │                                            │                        ├─ spawn background thread:
  │                                            │                        │    CONFIG.write() → set key_pair → store()
  │                                            │                        └─ return pk
  │                                            └─ Config::store_(&config, "")   WRITE RustDesk.toml
  │
  └─ RendezvousServer::start(...)                                [src/main.rs:35]
       └─ get_server_sk(key)                                     [src/rendezvous_server.rs:1229]
            └─ crate::common::gen_sk(0)                          [src/common.rs:107]
                 └─ reads id_ed25519 (relative path)
Why key_pair is written separately

Config::get_key_pair() operates independently of gen_sk(). It:

  1. Locks KEY_PAIR cache — misses
  2. Loads ~/.config/rustdesk/RustDesk.toml via Config::load_("")
  3. Finds key_pair.0 empty → generates sign::gen_keypair()
  4. Spawns a background thread that acquires CONFIG.write() and calls config.store() again with the populated keypair

Result: two keypair storage mechanisms coexist independently:

Mechanism Path Trigger Write time
gen_sk() (server-native) ./id_ed25519 RendezvousServer::start() +1.1ms
Config::get_key_pair() (shared lib) ~/.config/rustdesk/RustDesk.toml check_software_update() → T1 +0ms
Why the path lands in the data directory

On Linux, directories_next::ProjectDirs::from("", "", "RustDesk") resolves to:

/root/.config/rustdesk/RustDesk.toml

Because Docker is started as root with volumes: ./data:/root, the host path ./data/.config/rustdesk/RustDesk.toml is created.

The patch() function at config.rs:400 only redirects /root to /home/<user> when the effective user is not root — but inside the container the EUID is 0, so the redirect never triggers.

Why get_uuid() calls get_key_pair() on the server

get_uuid() at lib.rs:300 is a cross-platform machine identifier used by password security and fingerprinting:

pub fn get_uuid() -> Vec<u8> {
    #[cfg(not(any(target_os = "android", target_os = "ios")))]
    if let Ok(id) = machine_uid::get() {
        return id.into();
    }
    Config::get_key_pair().1  // fallback — client code, not server code
}

On Linux containers, machine_uid::get() reads /etc/machine-id or /var/lib/dbus/machine-id, which may not exist or may be inaccessible. When it fails, the fallback Config::get_key_pair().1 executes — invoking the client-side config system unnecessarily in a server context.

Notice the timestamp ordering from container logs confirms the call sequence:

[2026-05-13 07:55:40.524858 +00:00] INFO [libs/hbb_common/src/config.rs:895] Generated new keypair for id:
[2026-05-13 07:55:40.525990 +00:00] INFO [src/common.rs:121] Private key comes from id_ed25519
[2026-05-13 07:55:40.525997 +00:00] INFO [src/rendezvous_server.rs:1251] Key: SR5mXVPV7z5NzH3lFeOio0TRzJve4YF9iQTdR4fsgAA=
[2026-05-13 07:55:40.526001 +00:00] INFO [src/peer.rs:84] DB_URL=./db_v2.sqlite3

get_key_pair() (via check_software_update thread) fires first, followed by gen_sk() (via RendezvousServer::start). The ~1ms gap matches thread scheduling.


File Contents

data/.config/rustdesk/RustDesk.toml contains the encrypted ID and keypair:

enc_id = '00ZR49a5Jx88J4wzF7ZUQq6a1AFuW3EV7psg=='
password = ''
salt = ''
key_pair = [[123, 14, 63, ...], [117, 255, 245, ...]]
key_confirmed = false
[keys_confirmed]

Code References

File Line Description
libs/hbb_common/src/lib.rs 300-305 get_uuid() fallback to get_key_pair()
libs/hbb_common/src/config.rs 893-906 Config::get_key_pair() — loads + generates keypair
libs/hbb_common/src/config.rs 899-903 Background thread writes config via config.store()
libs/hbb_common/src/config.rs 500-513 store_path()confy::store_path_perms() (0o600)
libs/hbb_common/src/config.rs 615-637 Config::path()directories_next::ProjectDirs resolution
libs/hbb_common/src/config.rs 400-427 patch()/root/home/<user> redirect (skipped for root)
libs/hbb_common/src/config.rs 580-584 Config::store_() — stores after encryption
src/common.rs 107-155 gen_sk() — separate keypair mechanism for server
src/common.rs 130 sign::gen_keypair() — different from Config path

Impact

  1. Unexpected file in data directoryRustDesk.toml is a client config format, confusing for server operators
  2. Potential key confusion — two keypair storage locations exist (id_ed25519 vs RustDesk.toml), potentially diverging
  3. Permissions — file created with 0o600, owned by root (expected for root container)
  4. Data pollution — this file will appear in any backup/sync of the data directory

Suggested Fixes

Option A: Fix get_uuid() to not depend on Config::get_key_pair() in server context (recommended)

The get_uuid() fallback is designed for client machines where machine_uid may be absent. On a server binary, this fallback is inappropriate. Replace the fallback with a server-appropriate UUID source (e.g., generate once from a static salt + build info):

// libs/hbb_common/src/lib.rs
pub fn get_uuid() -> Vec<u8> {
    if let Ok(id) = machine_uid::get() {
        return id.into();
    }
    // Server context: use build-derived static UUID, not Config keypair
    crate::get_server_uuid()
}
Option B: Make Config::path() server-aware

Add a server-mode check that redirects ~/.config/rustdesk/ to the current working directory or an explicit server-specific path. Could be triggered by an environment variable like RUSTDESK_SERVER=1.

Option C: Skip Config::load() for server builds

The server binary does not need Config::load() behavior at startup. A compile-time #[cfg(feature = "server")] guard could skip the get_key_pair() fallback in get_uuid() entirely.


Reproduction

  1. On a Linux host, create an empty data/ directory
  2. docker run --rm -v $(pwd)/data:/root ghcr.io/rustdesk/rustdesk-server:latest hbbs
  3. Observe: data/.config/rustdesk/RustDesk.toml is created
  4. Check container logs — the Generated new keypair for id: log line appears

Version References

Component Version / Commit
rustdesk-server repo 815c728837b8a091c9feeeabb423d543be3a7f8d
hbb_common submodule 83419b6549636ee39dacef7776c473f5802e08d6
Docker image (investigated) ghcr.io/rustdesk/rustdesk-server@sha256:10818ec05b179039c6660f4d8e74b303f0db2858bbad2b18e24992ea22d54cd6

Report generated: 2026-05-13
Investigated by: AI Agent
Repos examined: rustdesk-server (local clone), rustdesk (local clone, pre-pulled)
(Generated by AI Agent)

コントリビューションガイド

このリポジトリのコントリビューションガイドは索引されていません

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

調査の方向性

libs/hbb_common/src/lib.rs、特に get_uuid() から始め、そのフォールバックを libs/hbb_common/src/config.rs の Config::get_key_pair() まで追跡してください。その経路を src/common.rs:107-155 および check_software_update() を経由する hbbs の起動チェーンと比較してください。ドキュメントに記載された Docker bind mount で再現し、hbbs が .config/rustdesk/RustDesk.toml を作成しなくなり、サーバーキーの動作は維持されていることを確認してください。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
docker, rust
領域
backend, devops
issue の種類
バグ
難易度
4/5
見積もり時間
3〜5日
活発さ
静か
明瞭さ
おおむね明確
初心者へのやさしさ
42/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。