Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 10046] ファイルの概要

コミット

このコミットは、Go言語のHTML/templateパッケージにおけるCSSエスケープ機能の重要なバグ修正を行っています。具体的には、CSS形式のエスケープ処理において、エスケープ後に続く空白文字を適切に処理する問題を修正しました。

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/fc3ce34903d5f86f398eda87ca6e334f483df604

元コミット内容

commit fc3ce34903d5f86f398eda87ca6e334f483df604
Author: Mike Samuel <mikesamuel@gmail.com>
Date:   Tue Oct 18 17:01:42 2011 -0500

    exp/template/html: fix bug in cssEscaper
    
    cssEscaper escapes using the CSS convention: `\` + hex + optional-space
    
    It outputs the space when the escape could be followed by
    a hex digit to distinguish a "\na" from "\u00aa".
    
    It did not output a space when the escape is followed by a space
    character so did not distinguish "\n " from "\n".
    
    Currently when doing lookahead, it does not distinguish spaces that
    will be escaped later by the same function from ones that will not.
    This is correct but suboptimal.
    
    R=nigeltao
    CC=golang-dev
    https://golang.org/cl/5306042

変更の背景

このバグ修正は、Go言語の実験的なHTMLテンプレートエンジン(後に正式版となる)において、CSS内での文字エスケープ処理に関する重要な問題を解決するものです。当時のGoは急速に発展しており、WebアプリケーションでのXSS攻撃を防ぐためのテンプレートエンジンのセキュリティ強化が急務でした。

特に、CSS内の文字エスケープは、HTML注入攻撃やCSS注入攻撃を防ぐ重要な防御メカニズムです。不適切なエスケープ処理により、攻撃者が悪意のあるコードを注入する可能性があり、これは深刻なセキュリティ脆弱性となります。

前提知識の解説

CSS文字エスケープの基礎

CSS仕様では、特殊文字は以下の形式でエスケープされます:

  • \ + 16進数の文字コード + オプションの空白文字

この仕組みにより、CSS内で特殊な意味を持つ文字(例:{};など)を安全に表現できます。

空白文字の重要性

CSS エスケープにおいて、空白文字は以下の役割を果たします:

  1. 区切り文字として: エスケープされた文字と後続の文字を区別する
  2. 曖昧さの解消: 16進数のエスケープ後に16進数文字が続く場合の境界を明確にする

例:

  • \na → これは \n + a なのか \naa なのか?
  • \n a → 明確に \n + + a と解釈される

HTML/templateパッケージの役割

Go言語のhtml/templateパッケージは、以下の機能を提供します:

  1. コンテキスト認識エスケープ: HTMLの各コンテキスト(HTML、CSS、JavaScript、URL)に応じて適切なエスケープを実行
  2. XSS防護: 悪意のあるスクリプト注入を防ぐ
  3. 型安全性: テンプレートの型チェック機能

CSS空白文字の定義

CSS仕様では、以下の文字が空白文字として定義されています:

  • \t (タブ)
  • \n (改行)
  • \f (フォームフィード)
  • \r (キャリッジリターン)
  • (スペース)

技術的詳細

修正前の問題

修正前のコードでは、以下の問題がありました:

  1. 16進数文字の後続チェック: エスケープ後に16進数文字が続く場合のみ空白を挿入
  2. 空白文字の見落とし: エスケープ後に空白文字が続く場合を考慮していない
  3. 曖昧なエスケープ: \n (改行+空白) と \n (改行のみ) の区別ができない

修正後の改善

修正後のコードでは:

  1. 包括的チェック: 16進数文字と空白文字の両方をチェック
  2. 明確な区別: エスケープされた文字と後続の文字を確実に区別
  3. 仕様準拠: CSS仕様に完全に準拠したエスケープ処理

コアとなるコードの変更箇所

1. isCSSSpace関数の追加

// isCSSSpace returns whether b is a CSS space char as defined in wc.
func isCSSSpace(b byte) bool {
    switch b {
    case '\t', '\n', '\f', '\r', ' ':
        return true
    }
    return false
}

場所: src/pkg/exp/template/html/css.go:35-42

2. cssEscaper関数の修正

// 修正前
if repl != `\\` && (written == len(s) || isHex(s[written])) {
    b.WriteByte(' ')
}

// 修正後
if repl != `\\` && (written == len(s) || isHex(s[written]) || isCSSSpace(s[written])) {
    b.WriteByte(' ')
}

場所: src/pkg/exp/template/html/css.go:52

3. テストケースの強化

// テストで双方向チェックを追加
recoded := cssEscaper(got1)
if got2 := string(decodeCSS([]byte(recoded))); got2 != test.want {
    t.Errorf("%q: escape & decode not dual for %q", test.css, recoded)
}

場所: src/pkg/exp/template/html/css_test.go:71-73

コアとなるコードの解説

isCSSSpace関数

この関数は、CSS仕様で定義されている空白文字を識別します:

func isCSSSpace(b byte) bool {
    switch b {
    case '\t', '\n', '\f', '\r', ' ':
        return true
    }
    return false
}
  • 目的: バイト値がCSS空白文字かどうかを判定
  • 対象文字: タブ、改行、フォームフィード、キャリッジリターン、スペース
  • 重要性: エスケープ処理の曖昧さを解消するために必要

cssEscaper関数の修正

修正の核心部分:

if repl != `\\` && (written == len(s) || isHex(s[written]) || isCSSSpace(s[written])) {
    b.WriteByte(' ')
}

この条件は以下の場合に空白文字を挿入します:

  1. repl != \``: バックスラッシュのエスケープでない場合
  2. written == len(s): 文字列の最後に到達した場合
  3. isHex(s[written]): 次の文字が16進数の場合
  4. isCSSSpace(s[written]): 次の文字がCSS空白文字の場合(新規追加

テストケースの改善

修正後のテストでは、エスケープとデコードの双方向性を確認:

recoded := cssEscaper(got1)
if got2 := string(decodeCSS([]byte(recoded))); got2 != test.want {
    t.Errorf("%q: escape & decode not dual for %q", test.css, recoded)
}

これにより、エスケープ処理が可逆的であることを保証しています。

期待される出力の変更

テストケースでは、以下の変更が期待されます:

// 修正前
"\x08\\9\\a\x0b\\c\\d\x0E\x0F"

// 修正後
"\x08\\9 \\a\x0b\\c \\d\x0E\x0F"

\9\cの後に空白文字が追加されているのが確認できます。

関連リンク

参考にした情報源リンク