はじめに
運用中のサーバーで使っているライブラリに CVE が公表された。
$ dnf update libexample
依存関係が解決しました。
行うべきことはありません。
完了しました!
修正パッケージがまだ来ていない。
こうなるパターンは大きく 2 つ:
- ベンダー(ディストリビュータ)の修正 RPM がまだリリースされていない(公表直後など)
- OS が EOL・パッケージがサポート対象外で、修正が今後も提供されない
この記事では、こういう状況で SRPM に上流の修正パッチを当てて自前で再ビルドし、配布して、後始末までやるフローを整理する。
前提は AlmaLinux 9(RHEL 系なら Rocky Linux などでもほぼ同じ)。パッケージ名 libexample・CVE 番号 CVE-2026-12345 は例示のダミー。
本当に自前ビルドすべきか
手順の前に釘を刺しておく。自前ビルドは最終手段だ。
先に確認すること:
- 設定で凌げないか:脆弱な機能の無効化、アクセス制限、WAF などの緩和策で攻撃条件を潰せるなら、まずそれで時間を稼ぐ
- ベンダーの修正予定:ディストリビュータのセキュリティアドバイザリページや Bugzilla を確認する。数日で出るなら待つ方が安全
# 該当 CVE の修正が更新リポジトリに来ていないか確認
dnf updateinfo list --cve CVE-2026-12345
それでもやると決めたら、代償を理解しておく:
- 自前ビルドしたパッケージはベンダーサポートの対象外になる
- そのパッケージの以後のセキュリティ追従は自分の責任になる
- パッチのバックポート判断を誤ると、脆弱性が塞がっていない・別のバグを入れる、が起こり得る
❌ 「野良ビルドの新バージョンで上書きすればいい」は危険。ディストリのパッケージは依存関係・設定パス・パッチ構成が調整されている。ベンダーの SRPM をベースに最小のパッチだけ足すのが原則だ。
全体フローの俯瞰
1. SRPM を取得する (ベンダーのソースパッケージがベース)
2. 上流の修正パッチを入手する (upstream の修正コミット)
3. spec にパッチを登録する (Patch 行 + Release 番号 + changelog)
4. mock でクリーンビルドする (rpmbuild で試行 → mock で本番用)
5. 検証環境で確認する (changelog・インストール・動作)
6. 社内リポジトリで配布する (createrepo で複数サーバーへ)
7. 後始末する (ベンダー修正が出たら置き換え)
👉 ゴールは「ビルドできた」ではなく、ベンダーの修正が出たら自然に置き換わる状態まで作ること。ここを設計しておかないと、独自ビルドが本番に残留し続ける。
準備:ビルド専用環境を作る
❌ 本番サーバーで rpmbuild するのは NG。ビルドには大量の開発パッケージが入り、本番の構成が汚れる。攻撃対象領域も増える。
**同じ OS バージョンのビルド専用 VM(またはコンテナ)**を用意して、そこで作業する。
# ビルドに使う基本ツール(dnf-plugins-core は download --source / builddep 用)
sudo dnf install rpm-build rpmdevtools dnf-plugins-core
# mock(クリーンビルド用。EPEL から入れる)
sudo dnf install epel-release
sudo dnf install mock
sudo usermod -aG mock $USER # 再ログインで反映
一般ユーザーでビルド用のディレクトリツリーを作る:
$ rpmdev-setuptree
$ tree ~/rpmbuild
/home/builder/rpmbuild
├── BUILD # ビルド作業領域
├── RPMS # 完成したバイナリ RPM
├── SOURCES # ソース tarball とパッチ
├── SPECS # spec ファイル
└── SRPMS # 完成したソース RPM
👉 rpmbuild も一般ユーザーで実行する。root でビルドすると、spec の記述ミスでシステムのファイルを直接上書きする事故が起こり得る。
SRPM を取得する
ベースにするのは、今インストールされているバージョンのベンダー SRPM。
# 現在のバージョンを確認
$ rpm -q libexample
libexample-1.2.3-4.el9.x86_64
# 対応する SRPM をダウンロード
$ dnf download --source libexample
libexample-1.2.3-4.el9.src.rpm
dnf download --source で見つからない場合:
# source リポジトリを明示的に有効化して再試行
dnf download --source --enablerepo='*-source' libexample
それでも出ないとき(EOL の古いバージョンなど)は、ディストリビュータのアーカイブ(AlmaLinux なら vault)から直接取る:
wget https://vault.almalinux.org/9.4/BaseOS/Source/Packages/libexample-1.2.3-4.el9.src.rpm
SRPM の展開と spec の構造
SRPM は「ソース tarball + パッチ + ビルド手順(spec)」の詰め合わせ。一般ユーザーで展開する:
$ rpm -ivh libexample-1.2.3-4.el9.src.rpm
~/rpmbuild 配下に展開される:
~/rpmbuild/
├── SOURCES/
│ ├── libexample-1.2.3.tar.gz # 上流のソース
│ └── libexample-1.2.3-fix-build.patch # ベンダーが当てているパッチ
└── SPECS/
└── libexample.spec # ビルド手順書
spec ファイルで最低限読むのはここだけ:
| 項目 | 意味 |
|---|---|
Version: / Release:
|
パッケージのバージョンとビルド番号 |
Source0: |
上流ソース tarball |
PatchN: |
適用するパッチの一覧(番号順) |
%prep |
ソース展開とパッチ適用の工程 |
%changelog |
変更履歴 |
libexample.spec の該当部分はこんな形をしている:
Name: libexample
Version: 1.2.3
Release: 4%{?dist}
Source0: https://example.org/releases/libexample-%{version}.tar.gz
Patch0: libexample-1.2.3-fix-build.patch
%prep
%autosetup -p1
👉 %{?dist} は .el9 に展開されるマクロ。Release: 4%{?dist} → 4.el9 になる。
👉 %autosetup -p1 は「ソースを展開して、PatchN を番号順に全部当てる」の短縮記法。ここに新しい PatchN 行を足せば自動で適用される。これが今回のフックになる。
上流パッチを入手して spec に登録する
修正コミットを特定する
CVE の修正コミットは、以下から辿れることが多い:
- NVD やディストリの Bugzilla に載っている「References」のリンク
- upstream プロジェクトのセキュリティアドバイザリ・リリースノート
GitHub でホストされているプロジェクトなら、コミット URL に .patch を付けるだけでパッチ形式で取れる:
cd ~/rpmbuild/SOURCES
curl -L -o CVE-2026-12345.patch \
https://github.com/example/libexample/commit/abcd1234.patch
パッチの中身は必ず目視する。修正コミットが本当にその CVE のものか(コミットメッセージ・変更内容)を確認してから使う。
spec に Patch 行を追加する
~/rpmbuild/SPECS/libexample.spec の既存 PatchN の後ろに追加する。番号はベンダーのパッチと衝突しないよう大きく飛ばす:
Patch0: libexample-1.2.3-fix-build.patch
Patch100: CVE-2026-12345.patch
%prep が %autosetup ならこれだけで適用される。古い spec で %patch を手書きしているタイプなら、%prep に 1 行足す:
%prep
%setup -q
%patch0 -p1
%patch100 -p1
パッチが当たるか確認する(-bp)
フルビルドの前に、%prep 工程だけ実行してパッチ適用を確認できる:
# ビルド依存を先に入れておく
sudo dnf builddep ~/rpmbuild/SPECS/libexample.spec
# %prep(展開 + パッチ適用)だけ実行
rpmbuild -bp ~/rpmbuild/SPECS/libexample.spec
Hunk #N FAILED が出たら、インストール中のバージョンと修正コミットのコードがズレている。
❌ ここでエラー行を機械的に書き換えて「当たるようにする」のは危険。バックポート(古いコードへの修正の移植)はコードを理解した上での判断が必要で、自信がなければ緩和策で凌いでベンダー修正を待つ方が安全だ。
Release 番号を設計する
自前ビルドで一番大事なのが、実はこの地味な作業。
RPM の更新判定は Version と Release の比較で決まる。ベンダーの次の修正が出たとき、自然に上書きされる番号を付けておく。
# 変更前(ベンダー)
Release: 4%{?dist}
# 変更後(自前ビルド)
Release: 4%{?dist}.0.1
これで 4.el9 → 4.el9.0.1 になる。比較すると:
-
4.el9.0.1>4.el9(インストール済みのベンダー版より新しい → 更新できる) -
4.el9.0.1<5.el9(ベンダーの次のリリースより古い → 自然に上書きされる)
比較結果は rpmdev-vercmp で機械的に確認できる:
$ rpmdev-vercmp 1.2.3-4.el9.0.1 1.2.3-5.el9
1.2.3-4.el9.0.1 < 1.2.3-5.el9
❌ Release: 999%{?dist} のような番号を付けるのは NG。ベンダーの修正が永遠に適用されなくなる。
最後に %changelog の先頭へ記録を残す(日付は英語表記・曜日付き):
%changelog
* Fri Jul 17 2026 Example Builder <builder@example.com> - 1.2.3-4.el9.0.1
- Apply upstream patch for CVE-2026-12345
👉 この changelog が後で「このサーバーのこれは自前ビルドか?」を判別する手がかりになる。省略しない。
rpmbuild でビルドする
まずは手元で通しビルド:
$ rpmbuild -ba ~/rpmbuild/SPECS/libexample.spec
成功すると RPMS / SRPMS に成果物ができる:
~/rpmbuild/RPMS/x86_64/libexample-1.2.3-4.el9.0.1.x86_64.rpm
~/rpmbuild/SRPMS/libexample-1.2.3-4.el9.0.1.src.rpm
よくあるエラー:
| エラー | 原因 | 対処 |
|---|---|---|
error: Failed build dependencies |
ビルド依存パッケージ不足 | sudo dnf builddep SPECS/libexample.spec |
Hunk #N FAILED / can't find file to patch
|
パッチが当たらない |
-p の階層数を確認。ズレが大きいならバックポート判断に戻る |
%changelog not in descending chronological order |
changelog の日付順・曜日ミス | 先頭に追加したエントリの日付・曜日を直す |
mock でクリーンビルドする
rpmbuild -ba が通ったら、配布用は mock でビルドし直す。
なぜ二度手間をかけるか:
- rpmbuild はホストに入っているパッケージをそのまま使う。手元にたまたま入っていたライブラリに依存した RPM ができてしまうことがある
- mock は毎回まっさらな chroot 環境を作り、spec の
BuildRequiresだけを入れてビルドする。「どこでも再現できるビルド」の保証になる
# mock はソース RPM を入力にとる
$ mock -r alma+epel-9-x86_64 --rebuild \
~/rpmbuild/SRPMS/libexample-1.2.3-4.el9.0.1.src.rpm
成果物はこちらに置かれる:
$ ls /var/lib/mock/alma+epel-9-x86_64/result/
build.log
libexample-1.2.3-4.el9.0.1.src.rpm
libexample-1.2.3-4.el9.0.1.x86_64.rpm
root.log
👉 -r に渡す設定名は /etc/mock/ 配下にあるものから選ぶ。対象サーバーと同じ OS・アーキテクチャのものを使う。
👉 ビルドが失敗したら result/ の build.log(コンパイルログ)と root.log(chroot 構築ログ)を見る。「rpmbuild では通るのに mock で落ちる」は、だいたい BuildRequires の記載漏れだ。
ビルド結果を検証する
本番に入れる前に、ビルド環境と検証環境で確認する。
1. パッケージのメタ情報を見る:
$ rpm -qp --changelog libexample-1.2.3-4.el9.0.1.x86_64.rpm | head -3
* Fri Jul 17 2026 Example Builder <builder@example.com> - 1.2.3-4.el9.0.1
- Apply upstream patch for CVE-2026-12345
2. rpmlint で静的チェック:
$ rpmlint libexample-1.2.3-4.el9.0.1.x86_64.rpm
👉 ベンダーの SRPM 由来の警告はもともと出ることがある。パッチを足す前の SRPM と比べて増えた警告がないかを見る。
3. 検証環境でインストールと動作確認:
# 検証環境(本番と同構成)で
$ sudo dnf install ./libexample-1.2.3-4.el9.0.1.x86_64.rpm
$ rpm -q libexample
libexample-1.2.3-4.el9.0.1.x86_64
そのライブラリを使うサービスを再起動し、アプリケーションの動作確認まで行う。可能なら脆弱性スキャナや PoC で脆弱性が実際に塞がったことも確認する。
社内リポジトリで配布する
対象サーバーが数台以上あるなら、RPM を scp で配って回るのではなく社内 yum リポジトリにする。
リポジトリサーバー(HTTP で公開できるサーバー)側:
sudo dnf install createrepo_c
# RPM を配置してリポジトリメタデータを生成
sudo mkdir -p /var/www/html/internal-repo/x86_64
sudo cp libexample-1.2.3-4.el9.0.1.x86_64.rpm /var/www/html/internal-repo/x86_64/
sudo createrepo_c /var/www/html/internal-repo/x86_64/
クライアント(対象サーバー)側に .repo を配る:
# /etc/yum.repos.d/internal.repo
[internal]
name=Internal Hotfix Repository
baseurl=http://repo.example.internal/internal-repo/x86_64/
enabled=1
gpgcheck=0
あとは通常の更新と同じ:
$ sudo dnf update libexample
Release 番号を正しく設計してあるので、ベンダー版 4.el9 より新しい 4.el9.0.1 が普通に選ばれる。
👉 gpgcheck=0 は「誰でもこのリポジトリに RPM を置けたら全サーバーに配れる」ということでもある。継続的に運用するなら GPG 署名(rpm --addsign)とリポジトリサーバーのアクセス制御を入れること。今回は緊急対応の一時リポジトリという前提。
👉 RPM を更新した後は createrepo_c の再実行を忘れずに。メタデータを作り直さないとクライアントからは見えない。
後始末:ベンダー修正が出たら置き換える
自前ビルドは配って終わりではない。ベンダーの正式修正に戻すまでが対応だ。
1. ベンダーの修正リリースを監視する:
$ dnf updateinfo list --cve CVE-2026-12345
ALSA-2026:XXXX Important/Sec. libexample-1.2.3-5.el9.x86_64
ディストリビュータのエラータページ(AlmaLinux なら errata.almalinux.org)の該当 CVE をウォッチしておく。
2. 修正が出たら、上書きされることを確認して更新:
# 番号設計どおり、ベンダー版が新しいと判定されることを確認
$ rpmdev-vercmp 1.2.3-4.el9.0.1 1.2.3-5.el9
1.2.3-4.el9.0.1 < 1.2.3-5.el9
# 普通に update するだけで置き換わる
$ sudo dnf update libexample
$ rpm -q libexample
libexample-1.2.3-5.el9.x86_64
置き換えたら、社内リポジトリから自前ビルドの RPM を削除して createrepo_c を再実行する。
3. 逆に「まだ上書きされたくない」場合は versionlock:
ベンダー修正の検証が済むまで自前ビルドを維持したい場合は、バージョンを固定できる。
sudo dnf install python3-dnf-plugin-versionlock
sudo dnf versionlock add libexample # 固定
sudo dnf versionlock list # 確認
sudo dnf versionlock delete libexample # 検証が済んだら必ず解除
❌ versionlock を掛けたまま忘れるのが最悪のパターン。セキュリティ更新が二度と入らないサーバーが出来上がる。掛けたら解除の期限をチケットなどで管理する。
4. 自前ビルドが残っていないか棚卸しする:
# Release にサフィックス .0.1 が付いたパッケージを探す
$ rpm -qa | grep '\.0\.1'
libexample-1.2.3-4.el9.0.1.x86_64
👉 自前ビルドしたパッケージ・対象サーバー・戻し完了日は一覧で記録しておく。数ヶ月後に「これ誰がビルドしたやつ?」と発掘されるのが一番困る。
まとめ
- 自前ビルドは最終手段。緩和策とベンダーの修正予定を先に確認する
- ベースはベンダーの SRPM。上流の修正コミットを
PatchNとして最小限だけ足す - Release 番号は
4%{?dist}.0.1のようにサフィックス方式にして、ベンダーの次のリリースで自然に上書きされるようにする - 配布用ビルドは mock で。ホスト環境に依存しない再現可能なビルドにする
- 配布は createrepo_c で社内リポジトリ化。ただし gpgcheck=0 のまま常用しない
- ベンダー修正が出たら戻すまでが対応。エラータを監視し、棚卸しできる記録を残す
👉 一連のフローは「緊急時に RPM を自分で面倒みる」ための道具立てそのもの。一度素振りしておくと、いざという時の選択肢が 1 つ増える。