1 ポイント 投稿者 GN⁺ 2025-08-25 | 1件のコメント | WhatsAppで共有
  • RFC 9839 は、ソフトウェア開発時にテキストフィールドへ含まれうる 問題のあるUnicode文字 を明確に定義している
  • このRFCは、異なる言語やライブラリ 間でそれらの文字の処理に一貫性が欠けることによって生じる問題を扱っている
  • 9839では、比較的問題の少ない 3つのサブセット を提案しており、選択的に活用できる
  • 既存の PRECISフレームワーク と比べて、適用がより容易でシンプルである
  • RFC 9839向けの Go言語ライブラリ もあわせて公開され、実運用での活用に役立つ

背景とRFC 9839の概要

  • Unicodeは、ほぼあらゆるテキストデータ処理で標準として使われている
  • しかし実際のデータ構造やプロトコル設計で、すべてのUnicode文字を許可すると問題が発生する
  • Paul Hoffman と筆者は、繰り返し起こるUnicodeの問題に対する明確な基準を示すため、IETFへの個人草案 を提出した
  • 2年間の議論を経て正式な標準として採択され、RFC 9839として公開された
  • この文書は、問題のある文字の種類、なぜ問題になるのか(技術的・標準的理由)、そして利用者が選べる3つのサブセットを詳しく説明している

RFC 9839の主な内容

  • ソフトウェアやネットワーク環境で テキストフィールド を設計する際に、必ず参照すべき文書である
  • RFC 9839は 10ページ構成 で、IETF標準文書としては簡潔な部類に入る
  • 主にソフトウェア開発者とネットワークエンジニアを対象に、わかりやすく説明されている

問題のあるUnicode文字の例

  • 例として、JSONの username フィールドに次のような文字列が入りうる
    {  
        "username": "\u0000\u0089\uDEAD\uD9BF\uDFFF"  
    }  
    
  • 各コードポイントが持つ問題点
    • U+0000 : 意味を持たない NULL文字 で、一部のプログラミング言語の動作を妨げる
    • U+0089 : C1制御コード(CHARACTER TABULATION WITH JUSTIFICATION) で、動作が複雑かつ一貫しない
    • U+DEAD : 対になっていないサロゲート文字 で、UTF-16の限界に由来する問題である。不適切なデータが発生する
    • \uD9BF\uDFFF (実際には U+7FFFF): Noncharacter であり、標準上は交換が禁止されている
  • このようなコードポイントは、データ構造やプロトコル内で 一貫した処理ができず、予期しないエラーを引き起こす
  • RFC 9839は、こうした問題文字を公式に定義し、除外すべき種類を明確に示している

JSONの設計と限界

  • これはJSONの作者である Doug Crockford の責任ではない
  • Unicodeが十分に成熟していない時期に設計されたため、文字集合を厳密に制限できなかった
  • 今では標準そのものを変更できないため、問題文字を経験的に除外する方法 が必要になる

IETFのPRECISフレームワークとの違い

  • 2025年のRFC 9839以前にも、IETFでは RFC 8264(PRECIS Framework) などさまざまな標準が提供されていた
    • このフレームワークは、国際化文字列の整形・適用・比較方法を詳しく扱っている
    • 43ページ構成で、背景説明と解決策が包括的 である
  • PRECISはUnicodeバージョンへの依存が強く、複雑で適用しにくいという欠点がある
  • RFC 9839 は簡潔で実用性を重視しており、新しいプロトコル定義時に 迅速に採用しやすい

RFC 9839のサブセットと活用例

  • 9839は、現実的な3つのサブセット(scalars、XML、assignables)を提示している
  • 各サブセットは、除外する問題文字の範囲が少しずつ異なる
  • 以下は、主要なデータフォーマットとRFC 9839のサブセットが問題文字をどう処理するかの表の要約である
    • CBOR、TOML、XML、YAML など一部のフォーマットは、サロゲートや制御文字を部分的に除外している
    • I-JSON はサロゲートとnoncharacterを除外する
    • 一般的なJSON、Protobufs は除外しない
    • XML、YAML は文字セットの特性上、noncharacterや制御コードを部分的にしか除外しない
      • 参考: XMLとYAMLは、Basic Multilingual Plane以外のnoncharacterは除外しない

Go言語向けRFC 9839ライブラリ

  • RFC 9839の3つのサブセットに対する 文字検証 をサポートする小さなGoライブラリが公開された
  • 十分にテストされているが、最適化はまだ進行中である
  • 実際の現場でのテストとフィードバックが歓迎されている

RFC 9839の意義と作業過程

  • RFC 9839は、共著者たちとの度重なるフィードバックを経て、15回以上の草案修正の後に正式公開された
  • 多くのコミュニティ専門家による議論と貢献を通じて、初期案よりはるかに完成度の高い文書 へと発展した
  • 「Acknowledgements」セクションに貢献者が記載されている

RFC個人提出の経験

  • RFC 9839は 個人提出(individual submission) として進められた
  • Working Groupを通す従来方式よりも、労力と手続きの負担 が大きい
  • Working Groupへの参加経験と比べると、従来方式のほうが効率的で勧めやすい

1件のコメント

 
GN⁺ 2025-08-25
Hacker Newsの意見
  • 問題を引き起こす文字があるのは明らかだと思うが、データ構造やプロトコルの設計者が、あらゆる種類の文字(適切にエスケープされたものですら)を恣意的に許可しない方向に傾くのが最悪のシナリオだと感じる。たとえば、ユーザー名の妥当性検査は別のレイヤーで処理すべきだと思う。ユーザー名を60文字未満、絵文字やzalgo文字は禁止、ヌルバイト禁止、などとチェックし、APIで適切なエラーを返す作業だ。JSONのパース段階で、事前の妥当性検証の代わりにこうした問題で失敗してほしくはない。もちろん、ユーザー名には明らかに不適切な文字クラスがある。しかし、タブ文字などが実際に使われるテキストファイルを送るなら、自分の言語のutf8 string 型で扱えるものはエンコード可能であってほしい。特にヌルバイトには用途が多く、実際JSONでもしばしば見かける。だが、制限された「正常な」Unicode集合だけを使うべきだというなら、各自がミニ標準を作るより標準があるほうがよいと思う。結論として、アイデア自体はよさそうだが、ブログ記事で挙げられていた論理にはあまり納得できない

    • 2025年時点では、低レベルのワイヤプロトコルで使う文字列表現として、実質的に擁護可能なのは次のいずれかだけだと思う

      • "Unicode Scalars"(整形式のUTF-16、Pythonの文字列型)
      • "潜在的に不正なUTF-16"(WTF-8、JavaScriptの文字列型)
      • "潜在的に不正なUTF-8"(バイト配列、Goの文字列型)
      • 上記のいずれかに「U+0000なし」オプションを追加(バッファオーバーフロー脆弱性以前に設計された言語・ライブラリと連携する場合)
    • 真面目な話、プレーンテキストファイルではC0(改行と、せいぜいHTを除く)およびC1文字を使わないでほしい。ANSIカラーマークアップのようなものを保存したがるのは理解できるが、その場合それは実際にはプレーンテキストではなく、一種のテキストマークアップ形式だ。Markdownに似ているが、C0範囲のエンコーディングを使う点だけが違う。データが cat コマンドなどできれいに見えるからといって、プレーンテキストだとは言えない。プレーンテキストでエンコードされたマークアップ形式が相互運用性のために多いことは認識している

    • データ構造やプロトコルで任意の文字群を禁止し始めるのが最悪だという意見自体、現実とかけ離れた考え方だと思う。本当に最悪なのは、パーサなどのソフトウェア欠陥によってセキュリティ侵害が起きることだ

    • ユーザー名にUTF-8を許可するシステムがあるのか疑問だ。プログラム的に操作または評価されるすべての識別子(ログイン用ユーザー名、パスワードなど)は必ずASCIIであるべきなのは当然だ。ISO-8859-1ですらなく、ASCIIのみを使うべきだ。Unicodeはこうした用途には向いていない。ユーザー名を表示する場合などは別だが、システムログインの識別子としては非ASCIIエンコーディングは全面的に禁止すべきだ。キーボードソフトウェアでさえASCIIを超えると視覚的表現についてUTF-8の一貫性を将来にわたって保証できず、OSや設定によってさらに混乱する。将来残されるバイナリとUnicodeを解釈するAIが一致する保証もない。また、一貫性という点でも、IVSの扱いや正規化(NFC/NFD/NFKC/NFKD)の問題をRFC 9839も記事も明確にスコープ内外として扱っているのか不明だ。目的セクションがまるで欠けているように見える。せいぜい「非文字コードポイント」がある、といった曖昧な言及しかない

    • そもそも、なぜユーザー名で絵文字を禁止しなければならないのか気になる

  • IETFが2025年までBad Unicode対応を待っていたわけではない、と言いたい。すでに以前から RFC 8264: PRECIS Framework でさまざまなBad Unicode問題を広範に扱っている。RFC 8265(リンク)、8266(リンク)などの関連RFCも参照すると役立つ。一般に、テキスト方向を変えられたり、入力機器ごとに異なるエンコードになり得るパスワードなどは、ユーザー名/パスワードに使うべきではない。こうしたRFCプロファイルによって安全に対処できる。この目的では、"failing closed"(より厳しく遮断すること)のほうが安全だ。新しい絵文字が出てきたとしても、ユーザー名で許可して全ページに影響を及ぼすより、むしろ禁止して保守的にいくほうを好む

    • それでも閉じすぎると、20年後にも20年前の絵文字がまだ未対応のまま残り、結局はユーザー不満だけが大きくなる
  • Unicodeには明らかに「良い」部分もあるが、例外的に除外すべき文字があることを理解しなければならないのは残念だ。言語の記録方式を包括的に受け入れようとして複雑になりすぎた結果だ。どの文字を特別扱いすべきかを常に考えなければならず、疲れる。だからUnicode文字列は独立したデータ単位だと考えて扱う。入力し、保存し、レンダリングし、データ同値性比較はしても、内容を解釈しようとはしない。文字列を連結したり加工したりすることにすら不安を感じる

    • Unicodeは終わりのないトリビアと悪い決定の深淵のようだ。たとえば、関連RFCには旧式のASCII制御文字(表示の混同のおそれ)への警告はあるのに、Explicit Directional Overrides のような致命的なセキュリティ問題を持つ方向制御文字には何の言及もない

    • 簡単な例でも、最初の文字列が孤立した絵文字修飾子で終わり、二つ目が修飾可能な絵文字で始まると、それだけでもう問題が起きる。より複雑なケースが増えるほど問題は大きくなる

    • 複雑さは大きいが、この種のもののうちサロゲートや制御コードは、言語記録の目的ではなく、過去との互換性のために奇妙な設計が残された結果だ

    • Unicodeは不便だが、以前の他のエンコーディング標準よりはまだましだと思う

  • 問題の大半は、無効なUTF-8バイト列を拒否するか、全体としてエラーを返すようにすればよいと思う。たとえばサロゲートなどは、もともとUTF-8では不正なので、utf-8を使う言語ならこうしたシーケンスに対してエラーを返すべきだ。実際に問題になるのは、「コードポイント」として問題のあるもの(非表示文字など)だと思う。これは不正バイト列とは明確に別概念として扱ったほうが有用だ

    • 十分に合理的だと思う。こうした選択はアプリケーション実装者の役目であり、汎用ライブラリが決めるべき事柄ではない。ユーザー名だけを扱うJSONパーサなど見たことがない
  • Unicodeはすでに各コードポイントのカテゴリ(General Category)を定義しており、奇妙な文字種を分類している。関連Wikipedia記事 を参照できる。たとえば、Pythonで unicodedata.category(chr(0)) は "Cc"(control)、unicodedata.category(chr(0xdead)) は "Cs"(surrogate)を返す

  • すべての "legacy control" 文字をリテラルだけでなくエスケープ文字列(例: "\u0027")まで除外するのはやりすぎだと思う。C1はあまり使われないのでよいが、C0文字の一部には実用例がある。escape、EOF、NUL などには明確な用途が残っていると思う

    • やや特殊なC0文字(U+001E Record Separator など)はデータストリームで有用だと思う。文書では禁止しても、ストリームデータには役立つ

    • プログラムのソースコードで form feed(U+000C)文字が使われているのを見たことがある。Emacsはもともとページ単位ナビゲーション用に対応しているので、そうしたものが含まれることがある

  • Unicodeが良いとは思わない。文字集合が何であれ、実際に使う文字種(制御文字、図形文字、最大長など)は結局それぞれのアプリケーションに合わせて決める必要がある。JSONなどで含めたり除外したりしても大きな効果はない。UnicodeでもASCIIでも他の文字セットでも、特定のサブセット(あるいは superset)に名前を付けることが便利な場合はあるが、それが皆にとって良い選択だと勘違いすべきではない。RFC 9839はいくつかのUnicodeサブセットに名前を付けてはいるが、自分が作るサービスに無条件で正しい保証はない。私の結論は、Unicodeをまったく使わない、あるいは強制しないことも検討すべきだということだ

    • 本当の問題は combining character だ。これによってUnicodeは文字集合から、文字を描写するためのDSLへと変わってしまった
  • 入力を制御すべきか、それとも信頼できない入力を安全に出力するためのデータ型(web+log+debug用)でラップすべきか考えている

  • 1つのグラフィックユニットに入れられるUnicodeスカラー値の個数に対する制限が標準にあればよいのにと思う。最後に見たとき(数年前だが)、標準にはそのような制限がなく、代わりにストリーミングアプリケーションではグラフィックユニットを128バイトに制限するよう勧告していただけだった。こうした限界を標準で明確にしておけば、実装はずっと容易になり、不必要な制約もなくなると思う

  • 実際に「制御文字がない」という前提だけでプログラムを書いて壊れたケースを経験したことがある(フォームフィードはページ区切りなどに、escape文字はターミナル用途などによく使われる)。「全部UTF-8だ」という仮定も破られることがある(古いデータファイル、ログなど)。テキストとして意味のある処理をしないなら、ただバイト列として内容を変えずに渡すのが最善だ。ただしMicrosoft Windowsのせいで、ときどきchar16_tシーケンスを渡さざるを得ないこともある。UTF-16はUTF-8と入出力が根本的に異なる。変換時には、外部データ→内部形式への変換で、それぞれWTF-8(UTF-16)、surrogate escape(UTF-8)の方式を使う必要がある。両方式を混在させることはできない