View in English

  • Apple Developer
    • 今すぐ始める

    「今すぐ始める」を詳しく見る

    • 概要
    • 学ぶ
    • Apple Developer Program

    最新情報

    • 最新ニュース
    • Hello Developer
    • プラットフォーム

    プラットフォームを詳しく見る

    • Appleプラットフォーム
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    • App Store

    特集

    • デザイン
    • 配信
    • ゲーム
    • アクセサリ
    • Web
    • Home
    • CarPlay
    • テクノロジー

    テクノロジーを詳しく見る

    • 概要
    • Xcode
    • Swift
    • SwiftUI

    特集

    • アクセシビリティ
    • AIと機械学習
    • App Intent
    • Apple Intelligence
    • ゲーム
    • セキュリティ
    • Xcode Cloud
    • コミュニティ

    コミュニティを詳しく見る

    • 概要
    • 「Appleに相談」イベント
    • コミュニティイベント
    • デベロッパフォーラム
    • オープンソース

    特集

    • WWDC
    • Swift Student Challenge
    • デベロッパストーリー
    • App Store Awards
    • Apple Design Awards
    • Apple Developer Center
    • ドキュメント

    ドキュメントを詳しく見る

    • ドキュメントライブラリ
    • テクノロジー概要
    • サンプルコード
    • ヒューマンインターフェイスガイドライン
    • ビデオ

    リリースノート

    • 注目のアップデート
    • iOS
    • iPadOS
    • macOS
    • watchOS
    • visionOS
    • tvOS
    • Xcode
    • ダウンロード

    ダウンロードを詳しく見る

    • すべてのダウンロード
    • オペレーティングシステム
    • アプリ
    • デザインリソース

    特集

    • Xcode
    • TestFlight
    • フォント
    • SF Symbols
    • Icon Composer
    • サポート

    サポートを詳しく見る

    • 概要
    • ヘルプガイド
    • デベロッパフォーラム
    • フィードバックアシスタント
    • お問い合わせ

    特集

    • アカウントヘルプ
    • App Reviewガイドライン
    • App Store Connectヘルプ
    • 近日導入予定の要件
    • 契約およびガイドライン
    • システムステータス
  • クイックリンク

    • イベント
    • ニュース
    • Forum
    • サンプルコード
    • ビデオ
 

ビデオ

メニューを開く メニューを閉じる
  • コレクション
  • すべてのビデオ
  • 利用方法
  • 概要
  • トランスクリプト
  • Swiftで記述するセキュリティクリティカルなコード

    Swiftでセキュリティクリティカルなコードを記述し、メモリセーフなソフトウェアを構築して脆弱性のカテゴリを排除する方法を学びましょう。このセッションでは、境界、ライフタイム、型、初期化、並行処理においてSwiftがどのように安全性を保証するかをご紹介します。Spanやコピー不可の型などの高パフォーマンスなプリミティブを確認し、Strict Memory Safetyを使って安全でないコード構造を監査する方法や、既存のCモジュールを段階的に移行する手順をご確認ください。

    このセッションは、「Appleに相談」のイベントである「アプリの強化:セキュリティを向上させるための必須戦略」の一部として実施されました。詳しい情報や関連セッションについては、ビデオ全編をご視聴ください。

    リソース

      • HDビデオ
      • SDビデオ
  • このビデオを検索

    Swift言語チームのDougです 同僚のFelixと共に メモリ安全性とSwiftについて お話しします メモリの安全性は セキュリティ上の大きな課題です 他のセッションでは CやC++のコードベースにおける メモリ安全性のバグ を発見したり、それらが セキュリティ問題への発展を防ぐ 対策が紹介されました しかし プログラミング言語そのものに メモリ 安全性の問題を定義する方法ほど これほど包括的で 効果的な方法はありません そこで まずはSwiftの設計が メモリ安全性をどのように実現している かを確認することから 始めます

    その後、パフォーマンス についてお話しし 安全性を保ちながら 低レベルコードの性能を引き出し 出すために、最近導入された Swiftの機能をどのように 活用すべきかについて解説します

    さて メモリ安全な言語への移行には 非常に長い時間がかかるでしょう そこで unsafeコードのカプセル化の ベストプラクティスや C系言語からSwiftへ 段階的に移行する方法を説明します

    さて まず第一に、メモリ安全性とは あらゆるエラーを防止するという 包括的な概念ではありません 優れた言語設計により 特定のエラーの発生を定義したり コードへの混入を 防ぐことができます しかし現実には、プログラミング上の エラーは発生してしまうものです したがって メモリ安全性とは プログラムが記述された通りに 動作することを 確実に保証します 一見すると 不自然に聞こえるかもしれません もちろん プログラムは記述された通りに 動作するものです しかし 先ほど お話ししたように、攻撃者が メモリ安全性のバグを悪用すると コードに書かれていない処理を 強制実行できてしまいます だからこそ メモリ安全性は セキュリティの根幹なのです というのも、CやC++のコードなど どこかに メモリ安全性のバグがあると プログラムにほぼ何でもさせてしまう 可能性があるからです

    さて メモリ安全な言語は このような予期せぬ動作を防ぎます その方法は2つあります 問題のあるコードの コンパイルを拒否するか あるいは実行時に チェックを挿入して、何か問題が 発生時にそれを 検出するかのいずれかです これらの戦略を 組み合わせることも可能です 実際 Swiftではコンパイル時と 実行時のチェックを併用しており これについては後ほ ど詳しく説明します ここでのルールはただ一つです メモリ安全性の問題が 起きる可能性がある場合、プログラムは 実行を継続しては なりません 破損状態で実行を続けると プログラミングエラーが メモリ安全性の脆弱性へと 発展する原因となります

    これについては以前にも触れましたが メモリの安全性は 言語が対処すべき 5つの軸に分類されます 具体的には、境界安全性 ライフタイム安全性や型安全性 初期化安全性、スレッド安全性です C系言語は、これらの軸のいずれに 対しても安全性を提供していませんが その一部に対処するのに 役立つ緩和策は存在します それでは Swiftが各領域に どのように対処しているのか 詳しく見ていきましょう

    境界安全性は最も理 解しやすい概念です これは、アクセスがメモリブロック内に 行われていることを確認するものです そのメモリブロックの外には 出ないようにします C言語のように 割り当てブロックを超えて 調整し、関係のない他の値を読み 取ったり変更したりすることで メモリ安全性の問題を引き 起こす可能性があります ここでは、Swiftコードで 同じ脆弱性を再現してみます 12個の要素を持つ配列があります ここで、15番目の要素に アクセスしようとします Swiftは、境界安全性について 説明した内容と同様に、これが即座に トラップされるよう、実行時の 境界チェックを行います これが最も簡単な例です ここから先がさらに興味深くなります ライフタイム安全性とは、メモリに アクセスする際、そのメモリが 依然として有効であることを 保証するものです 一方、C言語では、解放後使用 (Use-after-free) のような ライフタイムセーフティに 違反するコードを記述するのは 比較的簡単です これは、コードが割り 当てられたメモリを解放した後 その古いポインタを介して アクセスしようとするもので そのポインタはもはや全く 別のものを指している可能性があります Swiftは、明示的な解放処理 (free)をなくすことで 解放後使用(Use-after-free) の問題を解消しています したがって ここにコードがある場合 MyClassのインスタンスを割り 当てることができます それをローカル変数に格納します これを使ってメソッドを呼び 出すことができますが どこにも free はありません その代わりに そのインスタンスが 使用されなくなった時点で コンパイラが破棄処理を挿入します そして コンパイラは常に 正しい方法でそれを行うのです

    Swiftのコレクションクラスでも 同様の自動ライフタイム管理が 採用されています これには配列や辞書などが含まれます ここでは、最初の行で配列が作成され 使用されなくなると 自動的に解放されます

    Swiftでは、自動参照カウント(ARC ) と呼ばれる手法が採用されています この手法は、従来の ガベージコレクション手法に 比べて 技術的なトレードオフのバランスが 非常に優れているため 他の手法ではなくこれを選択しました Automatic Reference Countingがあれば 基本的にメモリ管理について 考えることなく、Swiftで プログラミングを行うことができます あらゆるパターンが機能するようで ほとんどの場合、ライフタイム について 考慮する必要はありません 一方 Automatic Reference Countingについては 非常に高速です パフォーマンスと メモリ使用量の両面で オーバーヘッド が非常に低くなっています また 従来のガベージコレクション方式で 処理の一時停止も 発生しません

    しかし ライフタイムの 安全性を維持するために 実行時のオーバーヘッドを ゼロにしなければなら ない場合もあります そこで Swiftでは 「 Noncopyable型」も提供されています これらは、一意の所有リソースに 対する所有権を定義するものです ここには Noncopyable型は モデルに制限があります 所有権について、より深く 考慮する必要があります その代わり、オーバーヘッドが ゼロになります Noncopyable型については この講演の後半で 改めて取り上げます

    型安全性とは、誤った型でメモリに アクセスすることを防ぐものです 繰り返しになりますが C言語では 型安全性の問題を容易に 引き起こす可能性があります 例えば メモリ内のこのセルに 整数を書き込んだとします 共用体や明示的な型変換で ファイルを読み戻すと 読み戻したりすることで 型混乱を引き起こす恐れがあります

    Swiftには、Cのユニオンや 型変換と同等の機能がありますが どちらも設計上 安全になるよう作られています Swiftの列挙型(enum)は識別型です 判別型ユニオン(Discriminated Union)とは どのオプションが現在有効であるかを エンコードしていることを意味します ここでは、リソース列挙型があります これは、ファイルを格納することも リソースアクセス用の 整数識別子を格納できます switch文を使用して パターンマッチを行うことで これらが判別型であるため 間違ったメンバにアクセス してしまうことは 決してありません これはペアアクセスです

    Swiftの型キャストも同様です こちらのSwiftコードでは クラス と1つのサブクラスが定義されています そのサブクラスは メソッドをオーバーライドし 独自の別のメソッドを導入しています ごく一般的な オブジェクト 指向プログラミングですね ここでは、ダウンキャスト を試してみます ここで asを使用することで 指定されたオブジェクトを Myサブクラスにダウンキャストします これにより、オプショナル 値が生成されます この値には Myサブクラスの インスタンスが含まれるか ダウンキャスト に失敗した場合は空になります したがって サブクラスのインスタンスである 場合 コードは if文の 本体内で実行されます その場合 安全な other メソッドを呼び出すことができます ダウンキャストに失敗した場合 オプショナルは空になります if文の本体は実行されません これにより、キャストに 起因する潜在的なメモリセーフティの 穴を排除できるだけでなく 誤ったキャストによるミスを 防ぐのにも役立ちます

    初期化安全性も 比較的わかりやすい例の一つです これは、メモリが初期化される前に 読み取られるのを防ぐものです C言語では これは必須ではありません そのため、ローカル変数を宣言した後 初期化前に直接 使用することも可能です Swiftで同じことをしようとすると Swiftコンパイラ はこの試みを拒否します 変数に代入や初期化が 行われていないことを検知し コンパイル時にエラー として拒否するのです したがって メモリ安全性の 問題は発生しません

    最後で最も扱いが難しいのが スレッド安全性です スレッド安全性の違反とは プログラム内のどこかでデータ競合が データ競合により メモリ安全性の問題が生じます そして これは、その言語が他のあらゆる点で 安全であっても 発生する可能性があります

    この例では、共有可能な 可変リソースがあります 関数「replaceResource」は そのリソースの値を変更し その型をこの識別子に設定します

    これが 1 つのスレッドで 実行されていると想像してください 一方 別のスレッドでは 同じ共有リソースを操作する useResource関数が 実行されています ここでデータ競合が発生した場合 一方のスレッドがそれを ファイルとして 認識している まさにその瞬間に別スレッドが 整数で上書きする恐れがあります つまり 実際のファイルインスタンス が 整数で上書きされてしまうのです Swift 6の並行処理モデルは 共有される可変状態への並行アクセスが 発生しないようにすることで こうした データ競合を防ぎます

    さらに Swiftでは 高レベルでデータ競合を引き 起こさない、並行処理を安全に 扱う方法も提供されています その一つがActor です Actorとは 自身の状態をカプセル化し 並行した変更から保護する型です ここでの resource変数は Actorの状態の一部となっている ため、Swift は その状態にアクセスする可能性のある useResourceと replaceResourceが 並行して実行されない ことを保証します これは Swiftの async/await モデルを用いて一貫して強制されます したがって Actorに対する メソッド の呼び出しは常に非同期となります これは、呼び出し元が その呼び出しを安全に行うために Actorが他のスレッド上で コードの実行完了を待つ 必要がある場合があるためです

    また Swift では データ競合を防ぐための低レベルの プリミティブも提供されています ここでの Mutex型は 一度に1つのスレッドのみが アクセスできるものを表しています

    Mutexに格納された リソースへのアクセスは データへのアクセスはすべて 一時アクセスを提供する withLock関数を通じて行われます クロージャ内部では Mutexによって 同時に 1 つのスレッドのみがその クロージャを実行できるよう 保証されます

    このように Swiftでは メモリ安全性が組み込まれています ここには 実際に体験してみないとなかなか 伝えきれないメリットがあります というのも、メモリ 安全な言語を使用する場合 単に「心配する必要がない」 というだけではないからです メモリ安全性のバグ混入や その探索を心配する必要がなくなり そうしたことを 全く意識しなくて済みます そのため、コードの正しさや メモリ 安全性とは関係のない他の課題に 全神経を集中させることができます

    さて C系言語から移行された方であれば このメモリ安全性がパフォーマンスの 低下を招くのではないかと 懸念されるかもしれません

    大まかに言えば、Swiftは ネイティブコンパイルされる言語です Swiftはメモリ安全性のモデルに 対して効率的な実装を提供しており 多くのプログラム にとってはそれで十分ですが

    UnsafeなCと同等の パフォーマンスが 求められる非常に低レベルの コードについては Swift 6.2で 役立ついくつかの安全な 抽象化が導入されました

    C言語の例を見てみましょう これは、ランレングス 符号化を使用する 画像のデコーダです ループ内では、入力バッファから 一度に4バイトずつ読み込んでいます これがカウントと ピクセルデータであり その後、結果を出力バッファに 書き出しています 手動で境界チェックが 行われていることがわかります また このCコードには 実際には検証されていない 多くの仮定が含まれています つまり countパラメータが 対応するバッファのサイズを正しく 表していると仮定しています また 入力バッファと出力バッファが 奇妙な形でエイリアスを起こさないこと そしてこのプログラムの実行中に 別のスレッドによって変更されたり 解放されたりしない ことを前提とします こちらが、同じコードをSwiftに 翻訳したものです 入力バッファは データと長さの両方を カプセル化した配列となっています そのため そのことを把握することができます また マルチスレッドプログラムであっても この関数の実行中にデータが消失したり 変更されたりしないことが保証されます

    現在 配列へのアクセスには境界チェックが 行われるため、エラーが メモリ安全性の脆弱性にならず 実行時にトラップされます さて パフォーマンスについて お話しすると約束しましたね それでは、それを見てみましょう ループ内で適切な境界チェックが 行われていれば 安全であることが証明された場合 コンパイラは境界チェックを完全に 最適化により チェックを削除できます

    さて SwiftのCopy-on-Write( COW) 配列は、その実装において 参照カウントを使用しています コンパイラは参照カウント処理を 最適化して削除できます 多く この例でも実際にそうしています しかし このappend操作は 配列に新しい要素を追加します 配列に十分な空き領域が ない場合 より多くの記憶領域を持つ 新しいメモリを割り当て 既存の要素をすべてそこに コピーする必要があります これは、以前の C 言語の実装よりも 処理速度が低下することになります 以前の実装では、単にポインタを介して ピクセルを出力するだけでした

    ここにはもう一つ問題があります この設計では、クライアント側でも 配列を用意せざるを 得なくなります 呼び出し側に独自のコピー作成を 要求することになります 例を挙げましょう 配列にデータがありますが 出力画像の幅と高さを 示すヘッダーと 単純なヘッダーがあり その後に実際にデコードしたい 画像データが続いています ここで主な問題となるのは decode関数の呼び出しのためだけに RLEピクセルデータを ピクセルデータのすべてをコピー しなければなら ない点です デコード関数はピクセル 配列全体を読み 込む必要があるため 余分なヒープ割り当てと画像データ 全体の コピーが発生してしまいますが これは絶対に避けたいところです

    これに関連する問題として デコード関数が結果の配列を常に ヒープ上に 割り当てるという点があります この特定の画像デコード処理に関しては それで問題ないかもしれません しかし ピクセルを特定の場所 たとえばすでに割り当てられている 固定サイズのフレームバッファなどに 格納することを求める 他の呼び出し元があるかもしれません その場合 結果をもう 一度コピーしなければなりません したがって この種の問題は 低レベルでパフォーマンスに非常に 敏感なコードベースにおいて 頻繁に発生する可能性があります ArraySliceやジェネリック コレクションを使用することで 部分的に対処することは可能ですが それらの扱いは やや難しい場合があります

    そこで登場するのが 新しい「span」ファミリーの型です span は 連続したメモリ領域への安全で オーバーヘッドの少ない アクセスを提供します Swift で UnsafeBufferPoin terを使用したことがある方なら Spanを その安全な代替品として 捉えることができます

    spanの背後にある重要な考え方は 自身が所有していない 連続したメモリ を参照する点にあります メモリ 上での表現がまさに その通りであるため ポインタに長さを加えたものと 捉えていただいても構いません spanは完全なメモリ 安全性を提供します コンパイラによってチェックされる 形でライフタイム安全性を確保し 実行時のオーバーヘッド は一切発生しません また 境界チェックによりすべての アクセスを検証することで 境界安全性も提供します しかし 最も重要な点は Spanが参照する記憶領域を 所有しないということです その代わりに 記憶領域を所有する 様々な型と連携して動作します copy-on-write 配列や 固定長のインライン配列を参照する Spanを取得することも可能です また unsafeポインタ型とも 連携します これは unsafe言語や Spanが導入される 以前のコードと連携する際に重要です

    span 型のファミリーは Swift 6.2 で導入されました しかし span は非常に重要であると 考えているため、これらの型を Apple のオペレーティングシステムのより古い バージョンにも遡って対応させました これにより、デプロイメントターゲット を 引き上げることなく 今すぐこれらを採用 することができます

    それでは、ランレングスデコーダ の入力に span を採用してみましょう

    大した手間は かかりませんでした パラメータの型を配列から span に変更しただけです これが機能するのは、span が配列と同じ 要素へのアクセス用 API を提供しているためです また このコードは単に データを読み取るだけであり データを変更しようとしている わけではありません コピーを返すこともありません 実際 この関数では他に 変更する必要はありませんでした さて 変更されたのは API 契約です 呼び出し元は 配列の代わりに span を渡す必要があります それでは、呼び出し元のコード に戻って その方法を見てみましょう

    まず すべての配列には Spanを生成する span プロパティがあります ですから、次のようにコードを記述して コンパイルさせることができます 単に末尾にspanを追加するだけです これで動作はしますが 依然として 新しい配列を作成しているため パフォーマンスは向上しません span を受け取る 前にすべての データをコピーしておけば さらに効率化できます そこで 代わりに 元の配列から span を取得し その中から必要なスライスのみを decode 関数に 渡すようにします 読み取り側のコードは 自分に関連する span のみを受け取りますが 余分な割り当てや コピーは一切行われず かつ全体を通じて メモリの安全性が維持されます

    この例を見ると、spanは単に 配列の代替品のように見えますが そうではありません Spanは 他者が所有する記憶領域への参照です したがって 実行時のオーバーヘッド なしに 安全性を確保するため span型自体にはいくつかの必要な 制限が設けられています

    つまり spanは「Nonescapable型」 と呼ばれるものです ~Escapable構文で 記述されます この同じ構文を使用して Spanと同様に 振る舞う独自のNonescapable型を 定義することもできます Nonescapable型を使用すれば 先ほど行ったように 常に関数の引数として 渡すことができます

    ただし 関数からSpanを 返すことはできません なぜなら、Nonescapable型の 値を返すには、そのライフタイムが 引数のいずれかに紐付けられている 必要があるからです

    これが何を意味するか firstRun関数の 本体を詳しく見てみましょう このコードは 先頭要素と一致する 要素の連続(run)を探しています そして最初の一致しない要素が 見つかった時点でループを終了します ここで重要なのは、そのロジック ではなく 最後にあるreturn文です さて ここでは何を返すのでしょうか? 受け取った dataパラメータから 直接抽出した Spanを返しています それは単に、その関連部分だけです これ自体は問題ありません つまり Spanを提供してくれた 呼び出し元が 結果として得られるSpanを 十分な期間 存続させてくれます

    しかし もしこのコードが代わりに dataのコピーを新しい配列に作成し 作成したと想像してみてください この配列はローカル変数に格納され そのコピーから Spanを返そうとします この場合、コンパイラは エラーを発生させます

    新しく作成された配列が ローカル変数に格納されているためです それが その配列を存続させている要因です 呼び出し元に対してローカル変数への スパンを返すことはできません なぜなら、関数を終了するとすぐにその ローカル変数は消滅してしまうからです したがって C言語の観点から 考えると 私が述べている制限事項に 聞き覚えがあるかもしれません ローカル変数のアドレスを取得する 場合を想像してみてください そのポインタについては、どこかに 保存されてしまい、関数が返った後に ローカル変数が消滅した 後も使用されてしまうことが ないように、細心の注意を 払わなければなりません Swift では、こうした 制限が言語の一部として組み 安全性を損なうミスを 防ぐことができます

    Span 自体は、メモリへの読み 取り専用アクセスを提供します また MutableSpan という 別の型もあり これは変更可能な アクセスを提供するため 読み取りだけでなく 書き込みも可能です

    先ほど説明した連続した コレクションのいずれからでも MutableSpanプロパティを使用して その格納領域に対する MutableSpanを取得した場合 期待どおり、添字指定を使って 要素を変更することができます

    ここで重要なのは MutableSpanが 基になるメモリへの排他アクセスを 必要とするという点です つまり あるメモリブロックを参照する MutableSpanがある場合 他の誰も、その同じメモリに アクセスすることはできません 書き込みも読み取りもできません

    したがって コード内で 同じ配列内部にアクセスする 2 つ 目の span を作成し そこで変更操作を行おうとすると コンパイラはここでエラーを発生させ 変更操作が行われている間は ストレージへの アクセスを一切防止します

    この排他性モデルは Swiftの根幹をなすものです 実は、Swiftの誕生当初から 存在しています これは mutatingやinout使用時の 安全性を保証します 確保するための仕組みです 実際にメモリ安全性の違反を引き 起こすような状況に 遭遇することは極めて 大半のSwiftプログラマは この モデルの存在すら知りませんが メモリ安全性の 最終防壁として機能します

    さて それでは ランレングスデコード関数において ヒープ配列を返す代わりに 不変のSpanを採用する 段階に入ります これは スパンを導入した時よりも 少し手間がかかるでしょう しかし ここでもまず関数の シグネチャから 確認していきましょう ここでのポイントは 呼び出し元に返すために 新しい ストレージを作成するのではなく 呼び出し元がこの出力 パラメータを通じて 結果をどこに配置すべきかを 指定する点にあります outパラメータが渡されるのは 可変Spanを修正する際 結果がそこに格納されるためです また 出力配列に追加するのではなく ピクセルデータがこのout パラメータを通じて 最終的な配置先に 直接書き込まれるようになった 点にご注意ください つまり この関数内ではもはや メモリ割り当ては行われません C言語で行ったのと同様に 読み取りおよび書き込み バッファの設定はすべて 呼び出し元に委ねられています 呼び出し元について言えば、実は以前 earlydecodeが 配列を返す前提に 依存していました それでは 戻ってその部分を見てみましょう さて この関数が行う 必要があるのは、独自のピクセル 配列を 割り当てることです そして その配列にMutableSpanを 渡して 読み取りコードが 結果のピクセルを埋めるようにします

    もちろん この呼び出し元は ヒープ割り当てを選択していますが 別の呼び出し元であれば 全く異なる選択をする場合もあります

    例を挙げましょう こちらの構造体は 320 × 240のピクセルから なるフレームバッファを表しています ヒープメモリの割り当てを避けるため インライン配列として表現されています

    画像のデコード処理は、Inout を使用して 渡されたフレームバッファへの 可変参照を受け取ります その後 それらのピクセルに対して MutableSpanを取得し を取得し、それを デコード処理に渡します

    処理内容がお分かり いただけるでしょうか デコード処理はフレームに 直接アクセスしています いいえ 余分なコピーもありません メモリの割り当てもありません spanを採用することは パフォーマンスとメモリ安全性の両面で 大きなメリットとなります Swiftのコードベースで spanを採用されることをお勧めします spanを採用すべき箇所をお探しの場合 パフォーマンスの観点からは 2つの着手点があります ArrayやDataを使用する 性能が重視されるコードを探します 余分なコピーがあれば Spanに置き換えます デコード処理で行ったように 代わりに Spanを使用してください コード内の UnsafeBufferPointer を置き換えます その名前が示す通り UnsafeBufferPointer型は メモリ安全性を保証しないため 使用は極力控えるべきです Spanは安全でない ポインタの代替手段です 多少のリファクタリングが 必要になります それでは、同僚のFelixにバトンタッチ し メモリの安全性を損なうことなく Swiftで安全でない 構文に対処する方法について さらに詳しくお話ししていただきます

    Doug ありがとう

    こんにちは Felixです セキュリティエンジニアリング チーム所属です 次の議題は、安全でない コードを安全に使用することです

    さて メモリ安全な言語こそが プログラミングの未来です

    Dougが説明したように spanのようなオーバーヘッドの低い プリミティブを使用することで 安全なコードは非常に高速になり 安全なコードからはメモリ安全性の バグが発生することはありません

    一方で、今日では安全でない コードが至る所に存在しています 文字通りです

    以前は使用が難しかった分野でも 安全な言語が普及しつつあるとはいえ 依然として数十億行もの安全でない コードが存在しています 数十年前からの 既知の事実です

    ゼロからの書き直しにリスクが伴うのは エンジニアリングの常識です 書き換えにはリスクが伴います 新しい実装によってバグが発生したり 再発したりする可能性があり 既存の実装が安全でない実装と同時に 進化し続けると 両者が競合することになります

    だからこそ 書き換えの範囲を小さく unsafeコードから 段階的に移行します そうすることで、リスク の管理がはるかに 容易になり、成功の可能性も 飛躍的に高まります

    そして これが今日重要となっている 理由は、Swiftには C C++ Objective-Cから の段階的な移行を可能にするよう 設計された独自の機能があるからです

    その最初のツールが、Strict Memory Safetyです Strict Memory Safetyを有効にする 以上に、安全でない コードを扱う安全な方法はありません これは非常に重要です

    Strict Memory Safetyにより unsafeコードの使用箇所が判明します これは非常に有用です なぜなら Swiftでは安全で ないものにはしばしば unsafeという名前が 付けられていますが 一部の操作は暗黙的に 安全でないものだからです

    例えば このコードでは 配列をコピーする関数が 2つの配列変数xとyを宣言し memcpyを使用して 一方から他方へコピーしています

    このコードでは memcpy が 何か危険なことを行うとは 明示されていませんが memcpyはC関数であるため 安全でないポインタを受け入れます x と y は暗黙的に 安全でないポインタに変換されます 明確な兆候がないまま メモリ安全性の バグが発生する恐れがあります

    Strict Memory Safetyで コンパイラが警告を出します これや unsafe構文を 使用しているにもかかわらず unsafeキーワードで 明示されていません 未注記のunsafeコード 全てに警告が出ます

    これは、呼び出しの前に unsafeキーワードを 追加することで解決されます

    ここで少し時間を割いて unsafe キーワードについて詳しく説明します コンパイラの警告に対処する以外にも 主に2つの役割があります

    第1に コード監査時の 警告として機能します unsafeキーワードは 潜在的に危険な処理が 行われていることを示す指標です

    そして第二に、さらに重要な点として コンパイラでは検証できない部分を デベロッパが自ら検証しなければなら ないことを思い出させる 役割があります

    Memcpyの例に戻りましょう 両方の配列が4バイトであるため このコードは正しく動作します しかし コンパイラは これが 呼び出しの前提条件だと認識できません unsafeキーワードは Memcpyが正しく使用されている ことを確認するよう促すものです

    Xcodeプロジェクトでこれを 有効にするには 「Swift言語オプション」内の「厳格な メモリ安全性」設定を探してください

    Swiftパッケージの場合 Strict Memory Safetyは、パッケージ記述に Strict Memory Safety 設定を追加することで有効になります

    さて Strict Memory Safety が有効になっており unsafeコードが 可視化されるようにはなりますが それでもunsafe機能の使用は 完全に避けるのが最善です

    安全でないコードが真に 必要となる ケースは、2つしかありません 1つ目は、安全でない ライブラリとの相互運用を行う

    場合、2つ目は、安全な プリミティブを 実装する場合です これらは同じ課題の 表裏一体です

    安全な言語での開発には 多くの利点があります 一部のタスクにおいては 安全でない実装が 最適な手段となる 場合もあります

    unsafeなライブラリは一般的で セキュリティの問題はさておき 依然として最も成熟している 可能性があるためです 安全でないライブラリを安全な言語で 書き換えることが可能な場合 それは常に 望ましいことですが、任意の期間内に 実現できるとは限りません 安全な書き換えには時間と専門知識が 必要であり、それらが 確保されるまでには時間がかかります Swift を利用すれば ライブラリの安全な利用を 担保することができます

    Dougは先ほど decode関数について紹介しました 仮に、この関数が外部のC ライブラリで 実装されており、簡単に書き 換えることができないとします

    Swiftがヘッダーを認識すると この関数をSwiftのunsafe インターフェースとして公開します 引数にソースポインタやサイズ 宛先ポインタ、および 宛先カウントを受け取ります 依然としてunsafe な値を使用しているため理想的とは 言えませんが、Swiftから 呼び出すことは可能です ただし、Strict Memory Safetyが 有効になっている場合 コンパイラは同じunsafe 構文を生成します 警告です

    Dougが示した安全な ラッパーを作成します このラッパーは入力として Spanを受け取り 出力として MutableSpanを返します このラッパーには unsafeという記述が 数多く含まれることになります これは Spanから ポインタを取り出す操作や それらのポインタを使用する 各操作について unsafeと マークする必要があるためです しかし セキュリティ監査は簡単に行えます

    このコードは 各Spanから unsafeポインタを取り出し 対応するカウント情報と共に C 実装に渡します

    Swiftインターフェースのユーザーを 監査する必要はありません なぜなら、Spanが渡され Spanは安全であり ここには間違いがないからです しかし もし間違いがあったとすれば それはこの実装側にあるでしょう Cの実装側にメモリ安全性の バグがある可能性もあります しかし decode関数が安全な実装になれば 呼び出し元のSwiftモジュールに 過失はなくなります このリスクは完全に排除されます

    さて 安全なプリミティブを 実装する側において 安全でないコードをラップする 際に起こりうる別の問題として 外部ライブラリが「ベンダー」 となっている可能性があります つまり 手動で作成・破棄する 何らかのリソースです

    ここでもう一度、実際の デコード関数を ご紹介します 単一のステートレスなデコード関数の 代わりに、RLを含む デコーダオブジェクトを作成し その後RLのdestroyメソッドで 破棄する必要がある ように修正しました

    また RLのdecode関数は 以前とほぼ同じですが 現在は最初の引数として デコーダを受け取るようになりました

    このパターンでは Copyable構造体には deinitを明示的に 指定する必要があります これは常にクラス を使って実装されてきました 現在 安全でないリソースを カプセル化する コードの多くは このような形になっています

    初期化関数はRLのi nit関数を呼び出し その後、初期化関数は RLのdestroy関数を呼び出します また デコード関数も 以前とほぼ同じものになります

    これは機能しますが 本来あるべきほど効率的ではありません

    まず クラスのインスタンスはすべて 動的に割り当てられます これは通常、長寿命の オブジェクトにとっては 問題になりませんが インスタンスを繰り返し作成・ 破棄すると 回避可能な malloc や free の呼び 出しが発生してしまいます さらに 動的割り当てを使って別の動的割り 当てをラップするのは よりコストがかかります セカンドクラスのインスタンスは 参照カウント方式で管理されます 複雑なメモリ管理でも 安全を確保できます オブジェクトの ライフサイクルが非常に 単純な場合でも、参照カウントの 処理にかかるオーバーヘッドが 発生してしまう可能性があります

    最後に、クラスインスタンス のすべての フィールドには 実行時にきめ細かな排他性チェックが 行われます これにより 構造体(struct)では不可能な より柔軟なエイリアシング操作が 可能になります しかし ここでもまた 単純な操作しか 行わない型の場合、何のメリットも 得られず、コスト を負担することになります クラスインスタンスは非常に 汎用性が高いですが 必要以上に冗長にな る可能性があります これらは小さなオーバーヘッドですが チェックを一切行わない 安全でない実装と比較すると そのコストはすぐに積み重なります

    Swift 6 以降では こうしたユースケースに非コピー可能な 構造体を使用できるようになりました

    構造体には、そのような動的割り 当ては必要ありません したがって オーバーヘッドの 原因の一つが解消されます また 構造体には排他性チェックがあり その多くはコンパイル時に 検証可能です そのためプログラム内の チェック数がそもそも少なく 最適化によって排除しやすくなります

    ただし、decodeOne関数における 参照型の柔軟性のおかげで Noncopyable構造体を共有する

    Noncopyable構造体は 共有に制約があります

    同じデコーダへの複数の参照を持ち そのいずれかを介して デコードメソッドを呼び 出すことに問題はありません

    しかし Noncopyable構造体で 同じことを行うとエラーになります コンパイラは オブジェクト A が B に移動された後 移動後の再使用を 即座に検知します

    つまり 単純なオブジェクト管理においては クラスに比べてNoncopyable構造体には 性能面で多くの利点があります 柔軟性が低いため、常にそれらを 使用できるとは限りません より特化されていますが その代わりに より予測可能な パフォーマンスが得られます

    最後にお話ししたいのは CからSwiftを 呼び出す方法です

    現在 すべての主要なプラットフォームには 安全でないCまたは C++のコアが 存在しており、すべての安全な言語は 何らかの方法でそのコアを呼び 出す必要があります Cとの混在は、安全な言語にとって 当然かつ期待される機能です とはいえ、実際には 困難な場合が少なくありません

    困難になる理由の一つは

    言語ごとに期待される挙動が異なり ある言語が別の言語を呼び出す際 双方の言語仕様を 満たす必要があります 例えば ガベージコレクションを 採用している言語において オブジェクトのポインタをCに渡す場合 オブジェクトが静止している間に ガベージコレクタ によって移動されないよう ガベージコレクタとの特別な連携が 必要になることがあります Cからの参照 また もう一つの理由は ほとんどの言語が互いを十分に 理解していないことです 例えば Cを呼び 出すことができる多くの言語では 相互運用にはグルーコード(Glue Code)が必要です これはCヘッダーファイルを 記述することを指します なぜなら、ターゲット 言語のコンパイラは Cヘッダー を読み取ることができないからです そのようなコード を書くのは面倒な作業です しかし幸いなことに Swiftは安全でない言語との相互運用を 念頭に置いて設計されています

    Swiftは、Cコンパイラ全体を 組み込むことで 開発上の複雑さを 大幅に解消します

    ブリッジングヘッダーを解析し インライン関数を含め ヘッダー内の任意の宣言を解釈して 利用可能にすることができます プロジェクトはブリッジングヘッダーに 任意のヘッダーを含めることができ

    Swiftは独自のヘッダー を生成することで Cコンパイラに対してその宣言を 利用可能にすることができます

    ここで、Dougが以前示した デコードの例を再び画面に表示します Dougは Spanを使用した実装が 最も柔軟な 選択肢です その実装は、まったく異なる方法で ピクセルを返す他の 2 つの関数に対して 共通のバックエンド として使用されていました 1 つ目は配列を返す関数 2 つ目はフレームバッファ構造体への 参照として 出力を返す関数です

    Swift 6.3 以降では Spanによる実装を Dougが最初に紹介したような C 関数のバックエンドとしても 使用できるようになりました これは、Dougがまだ 紹介していない実装です これを最後にじっく りと確認してください これからこれを削除し Swiftによる実装に置き換える予定です

    これを行うには、まず C関数と互換性のある プロトタイプを定義します Swift 関数が必要です

    ここには、入力ポインタ、アカウント 出力ポインタを受け取り アカウント内で、入力 ポインタと出力ポインタに 対してspanを作成する 準備済みのコードがあります そして この関数は 共通の読み取りコードである Swift バックエンドを呼び出します 関数をCに公開する方法には 2通りあります 1つ目は 関数に @_cdecl 属性を追加する方法です

    @_cdeclを使用する場合 Swiftはその型を適切な対応する C型に変換します 例えば SwiftのInt32型は int32_tに変換され C言語の基本整数型は それぞれ期待される 対応するC型(char や short、int など)に 変換されます なお int は ptrdiff_tに 変換される点にご注意ください これが 実装時にコードがsize_t ではなく pointerを受け取り 返す理由となります 実装する関数がブリッジングヘッダーで すでに宣言されている場合 宣言を出力する代わりに Objective-Cメソッドを @_implementationで実装するように とともに使用する場合と同様に @_cdecl / @_implementation を使用します これにより、コンパイラは ブリッジングヘッダーから decode関数の既存の宣言を 検索するよう求められます

    この方法を使用する場合 コンパイラは Swift のプロトタイプが Cのプロトタイプ と一致するかどうかを検証し 互換性がない場合は エラーを生成します

    単に @_cdeclを使用する場合 Swiftは引数の型を決定する 必要がありますが @_implementationを使用する場合は その他の変換にも対応できます 例えば Cのプロトタイプで size_t が 使用されている場合、Swift は uintの代わりに intを 受け取る実装も受け入れます さて ここで、先ほど何が 起きたのかを振り返る絶好の機会です 私の右側には、写真の中の ペットを識別する 「cats and dogs」 というC関数があります

    この関数は 画像構造体へのポインタと ペットのレコードのバッファへの ポインタを受け取ります 画像の解凍のために ピクセルメモリを管理し decode関数を呼び 出して画像を解凍します そして最後に、画像内の猫や 犬がどこにあるかを特定するために identifyCatsAndDogs を呼び出します

    以前は 先ほどDougが 示した右側のC言語実装の decodeを呼び出していました

    Swift側のコードを一切変更せず C言語の実装を利用することで catsAndDogs 関数は現在 安全なdecode 実装を使用するようになり

    Swiftからシームレスに 動作するようになりました これは Swiftコンパイラが Clangの decodeに 対して認識する C 関数プロトタイプと decodeの 実装を照合できるためです

    C 側で必要なのはこれだけです また @_implementationによる実装は C コードベースをSwift へ 移行したり、C から C側から利用する 場合に役立ちます 安全な言語で新機能を記述するための 優れたツールです Span について学んだところで 次は C と Swift 間の型と割り 込みの移動について見ていきましょう 次にすべきことは以下の通りです まず コード内でStrict Memory Safetyを 確保してください

    unsafe操作を 適切に管理できます 次に span を使用して連続したメモリに 安全にアクセス するようにしてください UnsafeBufferPointer の置き換えから始めます 次に 可能な場合は安全でない リソースをMove-only型やクラスで カプセル化し、安全な インターフェースを 作成してください

    そして最後に、Swiftの相互運用機能を 活用して、安全でない コードを段階的にSwiftへ 移行していきましょう ご視聴ありがとうございました メモリの安全性の欠如は 今日においてセキュリティバグの 最大の原因となっています 皆様と共により安全な世界を築くのを 楽しみにしております

デベロッパ向けフッタ

  • ビデオ
  • Meet with Apple
  • Swiftで記述するセキュリティクリティカルなコード
  • メニューを開く メニューを閉じる
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    • App Store
    メニューを開く メニューを閉じる
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • Icon Composer
    • SF Symbols
    メニューを開く メニューを閉じる
    • アクセシビリティ
    • アクセサリ
    • AIと機械学習
    • Apple Intelligence
    • オーディオとビデオ
    • 拡張現実
    • ビジネス
    • デザイン
    • 配信
    • 教育
    • ゲーム
    • ヘルスケアとフィットネス
    • アプリ内購入
    • ローカリゼーション
    • マップと位置情報
    • セキュリティ
    • Safari/Web
    メニューを開く メニューを閉じる
    • ドキュメント
    • ダウンロード
    • サンプルコード
    • ビデオ
    • ドキュメントのアーカイブ
    メニューを開く メニューを閉じる
    • ヘルプガイドと記事
    • お問い合わせ
    • フォーラム
    • フィードバックとバグの報告
    • システムステータス
    メニューを開く メニューを閉じる
    • Apple Developer
    • App Store Connect
    • Certificates, IDs, & Profiles(証明書、ID、プロファイル)
    • フィードバックアシスタント
    メニューを開く メニューを閉じる
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program
    • Mini Apps Partner Program
    • News Partner Program
    • Video Partner Program
    • セキュリティ報奨金プログラム
    • Security Research Device Program
    メニューを開く メニューを閉じる
    • Appleに相談
    • Apple Developer Center
    • App Store Awards
    • Apple Design Awards
    • Apple Developer Academy
    • WWDC
    最新ニュースを読む
    Apple Developerアプリを入手
    bilibili、LinkedIn、WeChat、YouTubeでフォロー
    Copyright © 2026 Apple Inc. All rights reserved.
    利用規約 プライバシーポリシー 契約とガイドライン