セキュリティ・キャンプ 2026 ネクスト 応募課題晒し
セキュリティ・キャンプ 2026 ネクスト 応募課題晒し
2026年のセキュリティ・キャンプ ネクストに参加できることになったので、応募課題晒しをする。
課題
セキュリティ・キャンプ2026 ネクスト 応募課題
以下について,フリーフォーマットで自由に記述し回答してください.
【必須回答】
■ あなたに関する問い(1,200 字以内)
あなたは今までどのようなことをやってきましたか.どのようなことができて,どのようなことが得意で,どのようなことに自信がありますか.
どのようなものを作りましたか.どのような情報を発信してきましたか.
どのようにしてそうしたことをやってきましたか.なぜ,そのようなことをやってきましたか.やってきてどう思いましたか.
参加できた場合,セキュリティ・キャンプ ネクストにどのようなことを期待し,どのようなことをやってみたいですか.
■ 課題への姿勢に関する問い(1,200 字以内)
自身で何らかの技術的な疑問を設定し,その疑問を解決しようと取り組み,その過程を示すことで,自身の技術力や課題に取り組むやりかたを説明してください.
(疑問の例:実行ファイルはどのような構造になっているのだろう? pingコマンドを実行すると何が起きるんだろう? オンラインゲームはどうやって通信対戦を実現しているのだろう? といったようなことです)
設定する疑問は何でも構いませんし,解決しなくても構いません.
解決できたかどうかではなく,いかに課題に取り組むかという点を評価します.
【選択回答:以下より1つ以上問いを選択し,回答してください】
■ 興味ある分野に関する問い
セキュリティ・キャンプ ネクストの講義の一覧を見て,その中から興味のある講義を選び,その講義で扱うテーマに対して自分が考えること,興味,疑問,課題,自分なりの考察などを説明してください.
その分野について知識があるかどうかではなく,いかに興味や疑問を持ち,課題を考え,自分なりに調べて考察するかといった点を評価します.
■ N1・N5「ハードウェア再現実装で学ぶアーキテクチャ解析」に関する問い
FPGA等を用いた開発経験がある方について,予期せぬ不具合に直面した際,原因究明から修正までどのように取り組まれましたか?
突破力を発揮したエピソードを語ってください.
■ N2「低レベル MN-Core グラフコンパイラ自作入門」に関する問い
MN-Core に関する資料を読み,一般的な CPU や GPU との違いに着目しつつ,MN-Core の良い点・得意な計算,悪い点・不得意な計算,そのほか面白いと感じたことについて考察して下さい.
MN-Core に関する資料として,例えば以下を参照することをおすすめします(全て目を通す必要はありません)
・MN-Core Challenge (1問目 ""Welcome""~ 8問目 ""Lesseq"")
https://mncore-challenge.preferred.jp/problems/
・新卒エンジニアが DEEP DIVE するMN-Core (P35 くらいまで)
https://speakerdeck.com/pfn/20241214_pfn_camphor_mncore_deepdive
更に,示した範囲外の内容や,MN-Core Challenge の参加記などの情報源を元に,考察を行っても構いません.
考察の際は,適宜,情報源とした MN-Core に関する資料を,硬くなくて良いので示して下さい.
また,この課題は理解が不正確でも特に減点は行いませんし,例え技術的に否定的な内容であっても評価対象になりますのでどしどし考察して下さい.
この課題の回答は,講義中でも回答者匿名で話題にしていこうと考えています.
■ N3「C++ の進化とライブラリ設計」に関する問い
技術を他人に説明することは,自身の理解を深く定着させる方法の 1 つです.
次の 3 トピックから 1 つを選び,解説記事を Markdown で作成してください.
① C++ の「クラス」 ② std::optional ③ std::span
【必須事項】冒頭に以下を明記すること
- 対象読者(例:他言語経験者 / 既存の C++ ユーザ)
- この記事で得られること(箇条書きで 2〜5 点)
【方針】仕様の網羅は不要です(むしろ避けてください).
読み切れる分量で,理解が積み上がる構成と「なぜ・いつ使うか」への納得感を重視します.
独自の切り口やコード例など,個性の発揮を歓迎します.
【任意事項】
- 参考にした資料があれば末尾への記載を推奨します
- ブログ・Zenn・Qiita 等での外部公開も歓迎します
【提出形式】以下のいずれか
- Markdown 本文
- 外部公開記事 / GitHub Gist / 公開リポジトリ等の URL
■ N4「TCP/IPプロトコルスタック自作入門」に関する問い
あなたはTCP/IPプロトコルスタックを自作し,短いテキストデータの送受信は問題なく成功するようになりました.
しかし,100MBの巨大なファイルを外部サーバからダウンロードするテストを行ったところ,Linux標準のネットワークスタックと比べて数十倍の時間がかかってしまうことが判明しました.
パケットキャプチャを見ると,通信自体は途絶えておらず,エラーも出ていません.
あなたは,この「通信は成功するが異常に遅い」という事象の原因として,TCP/IPの仕様上,どのようなボトルネック(実装の不備)が潜んでいると仮説を立てますか?
また,その仮説を検証するために,パケットのどの情報に注目し,どのようにデバッグを進めるか,過去のあなたの「ボトルネック調査」や「パフォーマンス改善」の経験を交えて記述してください.
■ N6「サイバーセキュリティに関する法規って本当に必要? | 自動車・製品セキュリティを例に,法規の重要性と限界を考える」に関する問い
自社製品に外部ソフトウェア(オープンソース/商用/無償ライブラリ等)を組み込むとします.それらのソフトウェアがセキュアであることをどう説明しますか?必要な考え方,説明に必要な情報,規制当局への説明方法など,第三者に納得してもらうことができる方法を考えてください.回答にあたっての方針や感想メモ
- N1・N5「ハードウェア再現実装で学ぶアーキテクチャ解析」に関する問い、以外はすべての設問に答えている。必須質問と N3 や N2 を書き終えたあとに、ここまでやって受からなかったら後悔しそうだと思って、受かる可能性を高めるために他の質問にも答えることにした。N1・N5 はFPGAの経験がないため、回答できなかった
- 土日に集中しておおよそ書き上げて、平日5日間くらいで少しずつ進めて、約1週間くらいかけて書き上げた
- N4 や興味ある分野に関する問いはあまりいい回答ができなかったが、他は満足のいく回答ができたと思う
- 文字数制限がある問いは、それがきつかった。とくにコードスニペットを入れると一瞬で制限に達してしまう。リンクは文字数に含めなくていいだろうと自己判断して、なんとかやりくりした
- 検証用スクリプトのコーディング、知らない知識の調査、ソースコードの調査などに AI(Perplexity Pro, Gemini 3.0 Pro, DeepWiki)を使った。Perplexity Pro と Gemini 3.0 Pro は学生向けの無料枠。問いの設定や回答の節構成、文章の組み立てはほぼ自分でやった
- N1・N5 や N3 はまったく専門ではないので、それらに関連する問いに答えるときは、基礎的な知識を雑にさらったうえで、できるだけ自分の詳しい領域に近い話題に引きつけるようにした
問1 あなたに関する問い(1,200 字以内)
あなたは今までどのようなことをやってきましたか.どのようなことができて,どのようなことが得意で,どのようなことに自信がありますか. どのようなものを作りましたか.どのような情報を発信してきましたか. どのようにしてそうしたことをやってきましたか.なぜ,そのようなことをやってきましたか.やってきてどう思いましたか. 参加できた場合,セキュリティ・キャンプ ネクストにどのようなことを期待し,どのようなことをやってみたいですか.
1199字で自分の経験をまとめた。 おおまかには、自分が作ったものを挙げながら、ウェブ開発に専門性があることを700字程度でアピールし、 残りの500字程度で、情報の収集、組織、保存、提供への興味が根底にあることを述べて、セキュリティキャンプネクストに参加したい理由を述べた。 やったことで公開できるものは、リンクを参考文献として末尾にまとめた。そのリンクは文字数に含めていない。
問2 課題への姿勢に関する問い(1,200 字以内)
■ 課題への姿勢に関する問い(1,200 字以内) 自身で何らかの技術的な疑問を設定し,その疑問を解決しようと取り組み,その過程を示すことで,自身の技術力や課題に取り組むやりかたを説明してください. (疑問の例:実行ファイルはどのような構造になっているのだろう? pingコマンドを実行すると何が起きるんだろう? オンラインゲームはどうやって通信対戦を実現しているのだろう? といったようなことです) 設定する疑問は何でも構いませんし,解決しなくても構いません. 解決できたかどうかではなく,いかに課題に取り組むかという点を評価します.
PyTorchで実装するとき「nn.Moduleを継承したクラスの順伝播処理に対して、いかにして正確に型付けするか」という疑問を設定した。完全な対応策は見つけられなかったが、ソースコードの調査や型システムの検証によって、対応案のトレードオフを明確にした。
背景
静的型付けはバグの未然防止や、コード補完による開発体験向上において重要である。PyTorchのnn.Moduleというクラスを継承して組み立てたモデルのクラスで順伝播するときはmodel()を用いる(参照1)。しかし、nn.Module.__call__の型ヒントはCallable[..., Any](参照2)であるため、型チェックができない。
案1: forwardを直接使う
__call__ではforwardを呼び出す前にいろいろなことをしているため、案1は避けたい。ソースを追うと_wrapped_call_implを呼び出し、その中で_compiled_call_impl / _call_implを呼び出している。前者は v2 で実装されたmodel.compile()を使ったときに呼ばれるし、後者ではフック(self._backward_hooksなど)の呼び出しや、トレースに使う_slow_forwardへの呼び出し切り替えをしている。
案2: __call__により継承側で上書き
その中身で継承元のsuper.__call__()を呼ぶ方法である。
class Model(nn.Module):
def __call__(self, x: Tensor) -> Tensor:
return super().__call__(x)しかし、自作のモジュールにしか使えない。
案3: Protocol を使った型付け
参照3を一部改変
P = ParamSpec('P')
R = TypeVar("R", covariant=True)
class _Module(Protocol[P, R]):
def forward(self: Self, *args: P.args, **kwargs: P.kwargs) -> R:
...
def apply_module(m: _Module[P, R]) -> Callable[P, R]:
return cast(Callable[P, R], m)しかし、この方法はmodel()以外のメソッドの補完が効かなくなる。この問題は、TypeScript の交差型 (ResNet & _Module<P, R>) のような機能がないため解決できない。
(1195字)
- https://docs.pytorch.org/docs/stable/generated/torch.nn.Module.html#torch.nn.Module.forward
- https://github.com/pytorch/pytorch/blob/70d99e998b4955e0049d13a98d77ae1b14db1f45/torch/nn/modules/module.py#L1918
- https://github.com/pytorch/pytorch/issues/74746#issuecomment-3600066341
興味ある分野に関する問い
■ 興味ある分野に関する問い セキュリティ・キャンプ ネクストの講義の一覧を見て,その中から興味のある講義を選び,その講義で扱うテーマに対して自分が考えること,興味,疑問,課題,自分なりの考察などを説明してください. その分野について知識があるかどうかではなく,いかに興味や疑問を持ち,課題を考え,自分なりに調べて考察するかといった点を評価します.
N1・N5『ハードウェア再現実装で学ぶアーキテクチャ解析』に興味を持った。とくに、「システム利用者向けの仕様情報を元にアーキテクチャの詳細を推測し同等機能を実現することで、システムの設計制約や先人の設計意図に思いを馳せ技術の謎を解き明かします。」という部分に、技術的な観点から、そして私の専門である図書館情報学的な観点から惹かれている。
技術的な観点
技術的な観点からとは、身につけるべきスキルとしてという意味だ。コードを書くときに下のレイヤの制約に気を付けるべき場合は多々ある。たとえば、R言語では for 文で繰り返しを書くよりもベクトルで書いたほうが高速に動作する(参照1)。全くのR言語の素人が見たら意味的に同じように見える処理でも、R言語の設計思想やR言語の処理系の特徴を知っておくことで多くの情報を読み取ることができる。制約や設計思想を知ることは技術者として必要であり、そしてそれらを知る方法を知ることができれば尚良い。とくにこの講義で扱うハードウェアの分野は私が深く触れてこなかった分野であるため、これまでとは全く違う、制約や設計思想を知るための方法論が得られると期待している。
図書館情報学的な観点
図書館情報学的な観点について述べる。私は図書館情報学を「情報の収集、組織、保存、提供を扱う学問」と捉えている。その図書館情報学的な観点において、あるシステムの設計思想や制約について、どのような情報があればどの程度の推測ができるのか、あるいはできないのか、という問いは非常に興味深い。たとえば、現代では情報の保存形態と表示形態が分離されているメディアが多い。具体的には紙の絵はそれだけでそこから意味を読み取ることができるが、SSDに保存された絵は別途閲覧のための機器が必要になる。これは、情報を保存するという観点からはあまり望ましくない。閲覧機器が失われれば情報へアクセスできなくなるからだ。実際に、国立国会図書館では失われつつあるメディアであるCD-ROMやフロッピーディスクへの対応方針を調査している(参照2)。もし閲覧機器が失われてしまったとき、どのような情報があれば閲覧方法が復元できるかを考えることは重要だ。上記のようなメタ的な視点もあわせつつ、予復習の期間を合わせて学べることが最も多そうな講義だと考えている。
- https://www.r-bloggers.com/2022/02/avoid-loops-in-r-really/
- https://www.ndl.go.jp/preservation/dlib/research
N2「低レベル MN-Core グラフコンパイラ自作入門」に関する問い
■ N2「低レベル MN-Core グラフコンパイラ自作入門」に関する問い MN-Core に関する資料を読み,一般的な CPU や GPU との違いに着目しつつ,MN-Core の良い点・得意な計算,悪い点・不得意な計算,そのほか面白いと感じたことについて考察して下さい. MN-Core に関する資料として,例えば以下を参照することをおすすめします(全て目を通す必要はありません)
・MN-Core Challenge (1問目 ""Welcome""~ 8問目 ""Lesseq"") https://mncore-challenge.preferred.jp/problems/ ・新卒エンジニアが DEEP DIVE するMN-Core (P35 くらいまで) https://speakerdeck.com/pfn/20241214_pfn_camphor_mncore_deepdive
更に,示した範囲外の内容や,MN-Core Challenge の参加記などの情報源を元に,考察を行っても構いません. 考察の際は,適宜,情報源とした MN-Core に関する資料を,硬くなくて良いので示して下さい. また,この課題は理解が不正確でも特に減点は行いませんし,例え技術的に否定的な内容であっても評価対象になりますのでどしどし考察して下さい. この課題の回答は,講義中でも回答者匿名で話題にしていこうと考えています.
私は MN-Core の「不確定要素なキャッシュ等が存在しない」という部分(参照1、p. 16)を非常に面白いと感じた。現在のプログラミング言語のコンパイラはキャッシュにうまく載せるためにパディングをしてアラインメントをしていると認識している。しかし、MN-Core ではソフトウェアでデータの流れを確定的に制御することを目指している。このパラダイムを徹底していった場合、より上のレイヤにも影響を与えるような変化になるのか、たとえば Python を書く人がそのパラダイムに合わせて書き方を変える必要があるか(たとえば、c言語の#pragmaのようなものや、データの取り扱いなど)、などを思考実験として考えてみたい。もちろん、グラフコンパイラによってモデル開発者がハードウェア特徴を意識しなくてもいい方が望ましい。一方で、今まで確率的にやっていたことを決定論的にやるならば、より多くの情報をコンパイラに渡さなければいけないように直感的には思える。また、グラフコンパイラのみではできない最適化があるのならば、その最適化を実現できる道を用意しておくことは重要だろう。 とはいえ、MN-Coreのコンパイラの詳細挙動を実験できるわけではないので、類似の ソフトなどをもとにしつつ、Python コードに気を遣う必要がある場合として考えられるアイデアを述べる。
Graph Breaks
まず、手に入るソースとしてtorch.compileを調べる(参照2)。チュートリアルでは graph breaks について述べられていて、グラフがサポートしていないコード(ifなど)が中間にあるとグラフが分割せざるをえず最適化ができないことが述べられている。このようなグラフで実行できる処理についての知識が開発者に求められる可能性は高い。あるいは、計算グラフが構築できる処理だけを集めたライブラリだけを作って、開発者にそれを使わせることで full graph を強制するというアプローチも考えらえる(以下のようなイメージ)。
class Model(mncore.Module):
def compilable_forward():
return mncore.build_compilable(
mncore.Linear(...),
mncore.Relu(...)
)計算グラフは構築できるが無駄な処理がある場合
次に、計算グラフは構築できるが無駄な処理がある場合だ。たとえば、以下の Linear を二つ重ねたコードの ONNX フォーマットは最適化の余地があるが、実際に ONNX にコンパイルしてもその最適化は実行されない(v1.21.0で確認)。(これはONNXの役割は中間表現で最適化はコンパイラ依存なので当然のことではある。)
class SimpleNN(nn.Module):
def __init__(self) -> None:
super().__init__()
self.fc1 = nn.Linear(784, 16) # 28x28 = 784
self.fc2 = nn.Linear(16, 10)
def forward(self, x: torch.Tensor) -> torch.Tensor:
x = x.view(x.size(0), -1)
x = self.fc1(x)
return self.fc2(x) # type: ignoreこのコード例ならば計算グラフから最適化できそうだが、もしかしたらより複雑な例で最適化できない場合や、探索範囲の広さによるコンパイル時間とのトレードオフにより最適化できることを見落とす場合があるかもしれない。その場合、Python を書く人はコンパイラが最適化しやすいコードを書く、あるいは自然にそのようになるコーディングプラクティスを広める必要がある。
入力値のドメイン知識
最後に入力値のドメイン知識だ。たとえば、入力値が0以下ならばより最適化できる計算があるとする。このとき、そのモデルが扱うドメインによって、あるいは前段の計算でその制約が満たされる場合はあり得るように思う。しかし、その前提は計算グラフというフォーマットでは伝達されないし、場合によっては普通に書いたコード上には表現されていない。これを伝えるために、開発者が@input_value_is_lt(0)のようなアノテーションをつけるようになる可能性はあるかもしれない。
- https://speakerdeck.com/pfn/20241214_pfn_camphor_mncore_deepdive
- https://docs.pytorch.org/tutorials/intermediate/torch_compile_tutorial.html
N3「C++ の進化とライブラリ設計」に関する問い
■ N3「C++ の進化とライブラリ設計」に関する問い 技術を他人に説明することは,自身の理解を深く定着させる方法の 1 つです. 次の 3 トピックから 1 つを選び,解説記事を Markdown で作成してください. ① C++ の「クラス」 ② std::optional ③ std::span
【必須事項】冒頭に以下を明記すること
- 対象読者(例:他言語経験者 / 既存の C++ ユーザ)
- この記事で得られること(箇条書きで 2〜5 点)
【方針】仕様の網羅は不要です(むしろ避けてください). 読み切れる分量で,理解が積み上がる構成と「なぜ・いつ使うか」への納得感を重視します. 独自の切り口やコード例など,個性の発揮を歓迎します.
【任意事項】
- 参考にした資料があれば末尾への記載を推奨します
- ブログ・Zenn・Qiita 等での外部公開も歓迎します
【提出形式】以下のいずれか
- Markdown 本文
- 外部公開記事 / GitHub Gist / 公開リポジトリ等の URL
トピック:② std::optional 対象読者: Optional 系の機能を実装しようとしている言語開発者 この記事で得られること:
- c++ の
std::optionalのメモリレイアウト - Rust の
Option<T>のメモリレイアウト - ニッチ最適化
- optional 系の機能の実装において考えること
はじめに
これは c++ の std::optional と rust の Option<T> のメモリレイアウトを実際に観察して、そこから言語実装者に対する示唆を与えることを目的としている。以下では、std::optional とOption<T> の使用方法や利点の知識を前提とする。
c++ の std::optional のメモリレイアウト
std::optionalでは以下のように、値の有無を判別するためのフラグを用いている(参照1)。
template<typename T>
struct _Optional_payload_base {
using _Stored_type = remove_const_t<T>;
_Storage<_Stored_type> _M_payload;
bool _M_engaged = false;
};
template<typename T>
class optional : private _Optional_base<T>, ...
{};実際の挙動を確認しても以下のようなメモリレイアウトになる(Appendix 1)。
[1] sizeof
bool: sizeof=1
optional<bool>: sizeof=2
vector<int>: sizeof=24
optional<vector<int>>: sizeof=32
[2] Byte dump engaged/disengaged
optional<bool> empty (2 bytes): 00 00
optional<bool> value=false (2 bytes): 00 01
optional<bool> value=true (2 bytes): 01 01
optional<vector<int>> empty (32 bytes): 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
optional<vector<int>> value=empty vector (32 bytes): 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00
optional<vector<int>> value={1,2,3} (32 bytes): c0 96 fa a4 ef 61 00 00 cc 96 fa a4 ef 61 00 00 cc 96 fa a4 ef 61 00 00 01 a3 bb 96 74 7b 00 00 bool のときは1バイト、vectorのときは8バイト分を追加で使っている。アラインメントの事情も合わせて最大8バイトの追加領域が使われている。
optional のプロポーザルでは次のようなフラグによる管理のイメージ実装を載せている(参照2)。
template <typename T>
struct optional
{
bool is_initialized_;
typename aligned_storage<sizeof(T), alignof(T)>::type storage_;
};次のプロポーザルでこの部分は削除されている(参照3)が、Removed most of the Rationale section.であり、とくにこのイメージ実装が否定されたわけではない。c++ではさまざまな実装があるが、原則としてこのようなフラグ型の管理が想定されている、ということだ。
Rust のOption<T>のメモリレイアウト
実際の挙動を確認すると以下のようになる(Appendix 2)
[1] sizeof
bool: sizeof=1
Option<bool>: sizeof=1
Vec<i32>: sizeof=24
Option<Vec<i32>>: sizeof=24
[2] Byte dump Some/None
Option<bool> None (1 bytes): 02
Option<bool> Some(false) (1 bytes): 00
Option<bool> Some(true) (1 bytes): 01
Option<Vec<i32>> None (24 bytes): 00 00 00 00 00 00 00 80 ce 3d b9 4b 02 7f 00 00 00 00 80 00 00 00 00 00
Option<Vec<i32>> Some(empty vec) (24 bytes): 00 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Option<Vec<i32>> Some({1,2,3}) (24 bytes): 03 00 00 00 00 00 00 00 60 5d 79 bc b5 60 00 00 03 00 00 00 00 00 00 00 Optionを使ってもsizeが増えていない。これは Option 型に対してニッチ最適化を適用しているためだ。たとえば、Option<bool> None (1 bytes): 02からわかるように None のときは02が置かれている。bool で使わない値をニッチ(あるいは Sentinel value)と呼び、そのニッチに型の情報を埋め込むことで Option の None を実現している。Vector の例は分かりにくいが、長さの最大値を表現する Cap の領域のニッチを使っている(Rust では Vec は通常 (ptr, len, cap) を持つ)。00 00 00 00 00 00 00 80の部分が Cap 領域のニッチになっている。ニッチを作って最適化をするためにNonZeroUsizeのような型も用意されている。
比較
以上の結果を踏まえて、なぜc++ではニッチ最適化をしていないのかを考察する。
1つめは、c++を取り巻く環境が原因という可能性だ。ニッチ最適化を使うためには、特定のデータ型でとり得ない値を知っておく必要がある。これはコンパイラと密に連携していないとできない機能だ。しかし、c++ にはいくつかのコンパイラがあり、理論上はそれぞれのコンパイラと標準ライブラリは独立している。そして、全体を規定している仕様でも詳細なメモリレイアウトは決まっていない。このようなc++の言語全体をとりまく環境が原因の可能性が高い。
2つめの可能性は実装コストとのトレードオフという可能性だ。前述したようにコンパイラと密に連携しないと実装できない機能だ。Rust におけるニッチ最適化の機能はコンパイラと密に結合している。1つめの課題を意思決定によってなんとか乗り越えたとしても、c++で同じように実装しようとすれば、コンパイラ側にも手を入れる必要があり、現在のテンプレートメインの実装よりもはるかに実装コストが上がっていただろう。ニッチ最適化を取り入れる検討はメーリングリストでなされている(参照4)が、2017年で議論は止まっており、実装される見込みはなさそうだ。
まとめ
optional の実装には大きく分けていくつかのアプローチが存在する。素直なのは、C++ の std::optional が採用しているようなフラグ方式である。値本体とは別に「値が存在するか」を示すフラグを保持する設計であり、実装が単純で ABI もシンプルで安定性が高い。一方で、型に対して常に追加の領域が必要になるため、サイズが増加するというデメリットがある。
Rust の Option<T> に見られるニッチ最適化は、型が本来取り得ない値(ニッチ)を利用して状態を表現する手法である。この方法では追加の領域を必要とせず、理想的にはサイズ増加を完全に回避できる。ただし、この最適化はコンパイラと密接に連携した仕組みを前提としている。
中間的な選択肢として、特定の条件を満たす型に対してのみ最適化を適用する方法もあるかもしれない。例えば Rust の NonZero 系の型のように、「特定の値が存在しないこと」を型レベルで保証することで、限定的にニッチ最適化を利用する設計である。
Appendix 1: C++ の std::optional のメモリレイアウトの確認コード
g++ -std=c++20 -O0 -Wall -Wextra -pedantic optional_layout.cpp -o optional_layout_O0以上のコマンドで、以下のファイルをコンパイルして実行した。
#include <array>
#include <cstddef>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <optional>
#include <string_view>
#include <vector>
template <class T>
void print_size(std::string_view name) {
std::cout << name << ": sizeof=" << sizeof(T) << "\n";
}
template <class T>
void dump_bytes(const T& x, std::string_view label) {
std::array<unsigned char, sizeof(T)> bytes{};
std::memcpy(bytes.data(), &x, sizeof(T));
std::cout << label << " (" << sizeof(T) << " bytes): ";
for (unsigned char b : bytes) {
std::cout << std::hex << std::setw(2) << std::setfill('0') << static_cast<unsigned>(b) << ' ';
}
std::cout << std::dec << "\n";
}
int main() {
std::cout << "[1] sizeof\n";
print_size<bool>("bool");
print_size<std::optional<bool>>("optional<bool>");
print_size<std::vector<int>>("vector<int>");
print_size<std::optional<std::vector<int>>>("optional<vector<int>>");
std::cout << "\n[2] Byte dump engaged/disengaged\n";
std::optional<bool> ob_empty;
std::optional<bool> ob_false = false;
std::optional<bool> ob_true = true;
dump_bytes(ob_empty, "optional<bool> empty");
dump_bytes(ob_false, "optional<bool> value=false");
dump_bytes(ob_true, "optional<bool> value=true");
std::optional<std::vector<int>> ov_empty;
std::optional<std::vector<int>> ov_empty_vec = std::vector<int>{};
std::optional<std::vector<int>> ov_values = std::vector<int>{1, 2, 3};
dump_bytes(ov_empty, "optional<vector<int>> empty");
dump_bytes(ov_empty_vec, "optional<vector<int>> value=empty vector");
dump_bytes(ov_values, "optional<vector<int>> value={1,2,3}");
return 0;
}$ gcc -v
gcc version 13.3.0 (Ubuntu 13.3.0-6ubuntu2~24.04.1)Appendix 2: Rust の Option<T>のメモリレイアウト確認コード
stable の v1.94.1、release モードで確認した。
use std::mem::size_of;
use std::slice;
fn print_size<T>(name: &str) {
println!("{}: sizeof={}", name, size_of::<T>());
}
fn dump_bytes<T>(x: &T, label: &str) {
let size = size_of::<T>();
let bytes = unsafe {
slice::from_raw_parts((x as *const T) as *const u8, size)
};
print!("{} ({} bytes): ", label, size);
for b in bytes {
print!("{:02x} ", b);
}
println!();
}
fn main() {
println!("[1] sizeof");
print_size::<bool>("bool");
print_size::<Option<bool>>("Option<bool>");
print_size::<Vec<i32>>("Vec<i32>");
print_size::<Option<Vec<i32>>>("Option<Vec<i32>>");
println!("\n[2] Byte dump Some/None");
let ob_empty: Option<bool> = None;
let ob_false: Option<bool> = Some(false);
let ob_true: Option<bool> = Some(true);
dump_bytes(&ob_empty, "Option<bool> None");
dump_bytes(&ob_false, "Option<bool> Some(false)");
dump_bytes(&ob_true, "Option<bool> Some(true)");
let ov_empty: Option<Vec<i32>> = None;
let ov_empty_vec: Option<Vec<i32>> = Some(Vec::new());
let ov_values: Option<Vec<i32>> = Some(vec![1, 2, 3]);
dump_bytes(&ov_empty, "Option<Vec<i32>> None");
dump_bytes(&ov_empty_vec, "Option<Vec<i32>> Some(empty vec)");
dump_bytes(&ov_values, "Option<Vec<i32>> Some({1,2,3})");
}- https://github.com/gcc-mirror/gcc/blob/a0d97fa4e447ec8ff8dc10f48a31b3c148bec758/libstdc%2B%2B-v3/include/std/optional#L307
- https://open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3672.html#overview
- https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3793.html
- https://groups.google.com/a/isocpp.org/g/std-proposals/c/fghaQHk8oe8
N4「TCP/IPプロトコルスタック自作入門」に関する問い
■ N4「TCP/IPプロトコルスタック自作入門」に関する問い あなたはTCP/IPプロトコルスタックを自作し,短いテキストデータの送受信は問題なく成功するようになりました. しかし,100MBの巨大なファイルを外部サーバからダウンロードするテストを行ったところ,Linux標準のネットワークスタックと比べて数十倍の時間がかかってしまうことが判明しました. パケットキャプチャを見ると,通信自体は途絶えておらず,エラーも出ていません. あなたは,この「通信は成功するが異常に遅い」という事象の原因として,TCP/IPの仕様上,どのようなボトルネック(実装の不備)が潜んでいると仮説を立てますか? また,その仮説を検証するために,パケットのどの情報に注目し,どのようにデバッグを進めるか,過去のあなたの「ボトルネック調査」や「パフォーマンス改善」の経験を交えて記述してください.
Windowサイズの上限
エラーは出ておらず通信は成功していることから、Windowサイズの上限が非常に小さいからではないか、と考えた。以前 Bluetooth 経由の Mac と Android のファイル転送の遅さが気になって、Android で HCI ログをとってボトルネック調査をしたことがある。Mac の PacketLogger がうまく動かず、Android のログダンプ自体が不安定だったので、わかったことは、途中で通信が詰まっているわけではなく安定しているが、安定した状態で送っていることだけだった。その状況は今回の課題によく似ている。当時 Bluetooth の通信方式を調べていて驚いたのは、credit と呼ばれる仕組みで送信可能量を制御しており、TCP/IPのような投機的な送信をしない方式が比較的有力な選択肢として存在するということだった。これは、インターネットのようなブラックボックスの経路ではないので、明示的に送信可能量を指定しても問題ないのだ、という説明を見た記憶がある。 逆に言えば、インターネット経由のときはある程度相手の状況を確認せずに投機的な送信をしないと問題があるということだろう。TCPでは速度とネットワークの混雑解消を両立させるために、フロー制御と輻輳制御の仕組みがあるが、速度が出ないのであればとくにフロー制御がうまく動いていない可能性が高い。 たとえば、RTTが0.1秒でWindow Scalingオプションが指定されていないとき、ウインドウサイズは最大65535であり、5.2Mbps程度の速度しかでない。
確認の方法
これを確かめるために、相手側のパケットで指定される window size に着目する。TCPヘッダの14-15バイトにあるウインドウサイズを確認し、かつ最初のネゴシエーションでオプションフィールドの Type 3 の Window Scaling が指定されているかを確かめる。これを確かめるには Wireshark などのパケットキャプチャソフトを使うか、作成したプロトコルスタックにログを仕込む(ただ、プロトコルスタックのパース自体にバグがあるかもしれないので、個人的にはパケットキャプチャをするだろう)。同時に輻輳制御側でウインドウサイズを抑える要因がないようにしつつ、他に迷惑がかからないプライベートネットワークの中でウインドウサイズが増加するかを確認する。
N6「サイバーセキュリティに関する法規って本当に必要? | 自動車・製品セキュリティを例に,法規の重要性と限界を考える」に関する問い
■ N6「サイバーセキュリティに関する法規って本当に必要? | 自動車・製品セキュリティを例に,法規の重要性と限界を考える」に関する問い 自社製品に外部ソフトウェア(オープンソース/商用/無償ライブラリ等)を組み込むとします.それらのソフトウェアがセキュアであることをどう説明しますか?必要な考え方,説明に必要な情報,規制当局への説明方法など,第三者に納得してもらうことができる方法を考えてください.
論点を明確にするために、具体的な状況を設定する。今回はIoT機器としてルーターを考える。また、説明の方法として、欧州をはじめ世界各国で広く採用されている ETSI EN 303 645 の認定を取得することを目指し、その中の外部ソフトウェアに関する規定である 5.2-3 を満たすための説明方法を考える。ETSI EN 303 645 は IPA が提供する日本語翻訳版を参照する(参照1)。
監視コストと対応コスト
まず、必要な考え方として、セキュアであることを保ち続けるためには時間軸における一点ではなく、線で考える必要がある。現時点で一定程度合理的な診断結果においてあるバイナリがセキュアであると判断されても、将来的に脆弱性が発見されればセキュアでなくなる。また、外部ソフトウェアはソースコードの全てを把握することは難しいことが多い。そのため、脆弱性情報の継続的な監視コストと、脆弱性が発覚したときの対応コストを払うことを、まずは受け入れる必要がある。そのうえで、その監視と対応の完全性と経済性をどのように高めていくかが議論の対象となる。
ETSI EN 303 645 の規定 5.2-3
ETSI EN 303 645 の規定5.2-3は以下の通りである。この規定を軸に考える。
規定 5.2-3 製造業者は、定められたサポート期間中、販売、製造された製品及び運用するサービス内のセキュリティ脆弱性を継続的に監視し、特定し、修正することが望ましい 注1: 製造業者製造業者は、製品に使用されるすべてのソフトウェア及びハードウェアの構成要素に対して当然払うべき注意を払うことが期待されており、これには製品の機能をサポートするために関連サービスを提供する選択されたサードパーティに関する当然払うべき注意も含まれる。 ソフトウェアソリューションには、多くの場合、オープンソース及びサードパーティのソフトウェアコンポーネントが含まれている。すべてのソフトウェアコンポーネントとそのサブコンポーネントのリストを作成して管理する事は、製品の脆弱性を監視できるための前提条件である。ソースコードとバイナリをスキャンし、製品で使用されているサードパーティのコンポーネントとバージョンを識別する、いわゆるソフトウェア部品表 (SBOM) を作成するためのさまざまなツールがある。この情報は、識別された各ソフトウェアコンポーネントに関連するセキュリティ及びライセンスのリスクを監視するために使用される。(なお、この後に脆弱性情報の開示と共有についての説明が続く) (参照1)
脆弱性の監視と特定
ここからわかるように、外部ソフトウェアを使うときにまず必要なのは、そのリストを脆弱性の把握に資するフォーマットで把握することである。このリストをSBOMと呼ぶ。SBOMには SPDX や CycloneDX といった形式があるが、いずれもコンポーネントの識別子やバージョンが含まれている。それらを用いて GitHub などが提供する脆弱性データベースと照合することで発見された脆弱性を早期に発見できる確率が高くなる。npm などの主要なエコシステムのOSSであれば、パッケージ管理ツールや GitHub などのリポジトリホスティングサービスから取得することができる。なお、ここでは脆弱性データベースが脆弱性を網羅していることを前提としているが、未発見の脆弱性もあるため現実的には無理筋な仮定だ。緩和策として、定期的に質の高い脆弱性診断をすることが望ましいだろう。
脆弱性の修正
その上で、脆弱性が発覚したときの対応方針が問題となる。外部ソフトウェア、とくにOSSへの依存があるとき、依存を再帰的に辿ると膨大なコンポーネント数になることが多い。つまり、発見される脆弱性もそれに比例して多くなる。それぞれについて、重要度を判定して対応の有無や、対応するとしたときの緊急度を決めて、対応するべきものはエンジニアが対応し、テストをして、製品を更新する必要がある。一応脆弱性の危険度をスコアリングしているデータベースサービスは多いが、特定の製品におけるその脆弱性の危険度とは相関しないこともある。加えて、複数のコンポーネントの脆弱性を組み合わせることで危険度が増す場合もある。そのため、実際に exploit できるかどうかで判断するのが望ましいのだが、それは非常にコストが高い。以上より「自分の機器にとっての危険度」を適切に評価することは非常に難しい。この問題については、LLMをエージェンティックに用いる環境を構築することで負荷を減らせるかもしれないが、決定的な解決策は見つかっていないように思う。 仮に脆弱性の危険度が適切に評価できたとして、それをどのように展開するまでのプロセスも重要な検討事項だ。まず、脆弱性に対応するということはコードを更新するということであり、更新後の動作を短時間で保証する必要がある。そのためには適切なテストフローを整える必要があり、とくに自動テストでどこまで保証できるかが問題となるだろう。また、更新したソフトウェアを配布するときに、ユーザー側にできるだけ負担をかけず、かつ真正性を検証できる方式で配布する必要がある。 以上のようなフローを構築して文書化し、ポリシーとして公開、脆弱性の対応方針や修正までの期間などを明示することで、ステークホルダーに対して規定5.2-3を満たしているという説明ができるようになるだろう。
アタックサーフェスの考慮
なお、この回答では認定基準を取得すればセキュアであるし他者に説明できるという立場で議論をした。しかし、認定基準の試験の完全性が保証されていない以上、認定基準を取得したからといってセキュアであることにはならない。たとえば、現在フロントエンドの有名ライブラリである axios の開発者の端末がソーシャルハッキングにより侵害され、悪意のあるコードを含んだ axios が正式なバージョンとしてリリースされたという、開発プロセスに対する攻撃からの提供物の侵害という事例がある。それを踏まえて、製品単体だけでなく、柔軟にアタックサーフェスを考え、対応していくことが大切だろう。また、そもそも論として、その外部ソフトウェアは必要かどうか、外部ソフトウェア自体の開発状況や信頼性はどうかを評価する必要もある。