私のチームが最近発見した
チームはオープンソース プロジェクトを常に監視して新しい脆弱性や悪意のあるパッケージを発見し、それらをより広範なコミュニティと共有して全体的なセキュリティ体制の向上に役立てています。
この 2 部構成のブログ シリーズでは、脆弱な Xpm ファイル形式の内部動作の詳細を提供し、これらの脆弱性の悪用について詳しく説明します。
Xorg X11 は、X Window System とも呼ばれ、Unix 系オペレーティング システムでのグラフィカル ユーザー インターフェイスの作成と管理を可能にするオープンソースグラフィカル サーバー プロトコルです。ネットワーク環境でグラフィカル アプリケーションを実行し、Windows を管理し、ユーザー入力を処理するためのフレームワークを提供します。
libx11 パッケージは、クライアント アプリケーションがデスクトップ上でデータをレンダリングして表示するために必要な必須の共有ライブラリを提供します。
libXpm は、 X Pixmap (XPM) 形式でイメージを読み取り、書き込み、表示する関数を提供します。
XPM は主に、透明ピクセルをサポートするアイコン ピクスマップを生成することを目的としています。これは XBM 構文に基づいており、XPM2 形式のプレーン テキスト ファイルにすることも、C プログラミング言語構文を使用することもできるため、C プログラム ファイル内に含めるのに適しています。
XPM イメージ形式 – バージョン
前任者 – XBM
XPM (X PixMap) が 1989 年に登場する前には、XBM 形式 (X BitMap) がありました。
プレーンテキストのバイナリ イメージ形式。X GUI で使用されるアイコンとカーソルのビットマップを保存するために使用されます。
XBM ファイルは C ソース ファイルとして構造化されています。これが、今日のほとんどの画像形式との主な違いです。そうすることで、アプリケーションに直接組み込むことができます。ただし、これと圧縮を使用できない(1 文字から 1 バイトのマッピング)ため、生のピクセル データよりもはるかに大きくなります。
X BitMap (XBM) データは、生のモノクロ ピクセル情報を格納する一連の静的な unsigned char 配列で構成されます。
たとえば、次の C コードは、イメージ内のhello
の XBM ファイルです。
#define hello_width 35 #define hello_height 25 static char hello_bits[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x88, 0x00, 0x00, 0x10, 0x00, 0x88, 0x00, 0x00, 0x10, 0x00, 0x88, 0x00, 0x00, 0xD0, 0xE1, 0x88, 0x78, 0x00, 0x30, 0x13, 0x89, 0xC4, 0x00, 0x10, 0x12, 0x89, 0x84, 0x00, 0x10, 0xF2, 0x89, 0x84, 0x00, 0x10, 0x12, 0x88, 0x84, 0x00, 0x10, 0x12, 0x88, 0x44, 0x00, 0x10, 0xE2, 0x89, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
通常の画像ファイル形式ヘッダーの代わりに、XBM ファイルには#define
ステートメントがあります。最初の値のペアは、ビットマップのピクセル寸法を定義し、高さと幅を示します。
X BitMap (XBM) 画像データは、静的配列内に格納されるピクセル値の連続シーケンスとして構造化されています。各ピクセルが 1 つのビット (0 は白を示し、1 は黒を表す) でシンボル化されているとすると、配列内の各バイトには 8 つの個別のピクセルのデータが含まれます。特に、最初のバイトの最下位ビットは、ビットマップ内の左上のピクセルのアンカーとして機能します。
XPMバージョンに対応
1989 年に初めてリリースされたこの形式は、上で概説した XBM 形式と多くの類似点を共有しています。 XBM はモノクロでしたが、XPM1 では画像にカラーが導入されました。
色に追加のマクロと変数を使用し、ピクセル データのバイトを文字に置き換えます。
たとえば、次は XPM1 形式の同じhello
画像です。
/* XPM */ static char *_hello[] = { /* columns rows colors chars-per-pixel */ "35 25 2 1 ", " c white", "bc black", /* pixels */ " ", " ", " ", " ", " ", " ", " ", " bbb ", " bbb ", " bbb ", " b bbb bbb bb bbbb ", " bb bb bbbbb bb ", " bbbbbbbb ", " bb bbbbb bbbb ", " bbbbbbb ", " bbbbbbb ", " bb bbbb bb bbb ", " ", " ", " ", " ", " ", " ", " ", " " };
ここで、 XFACE_ncolors
画像内の色の数を示し、 XFACE_chars_per_pixel
ピクセルあたりの文字数を示します。
各a
白の色「#ffffff」に置き換えられ、各b
黒の色「#000000」に置き換えられます。
1 年後の 1990 年に、状況を改善するためにフォーマットの 2 番目のバージョンが設定されました。これにより作業が簡素化され、イメージ形式からすべての C コードが削除されました。
簡略化した構造:
! XPM2 <Header> <Colors> <Pixels>
ヘッダー行は、XPM1 の #define ステートメントと同様にイメージの寸法を示します。
色のセクションでは文字の値を定義します。
新しい型の概念が導入されました。
c
– はカラーピクセルを表します
m
– はモノクロを表します
g
– グレースケール用の氷
s
– はシンボリックです
シンボリック機能は、コンテキストに基づいて色を割り当て、読みやすいようにそれらの変数名を作成するために使用されます。
前の例で示したものと同じhello
を含む XPM2 イメージの例:
! XPM2 35 25 2 1 ac #000000 bc #ffffff aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaabaaaaaaaaaaaaaabaaabaaaaaaaaaaa aaaabaaaaaaaaaaaaaabaaabaaaaaaaaaaa aaaabaaaaaaaaaaaaaabaaabaaaaaaaaaaa aaaababbbaaaabbbaaabaaabaaabbbbaaaa aaaabbaabbaabaaabaabaaabaabaaabbaaa aaaabaaaabaabaaabaabaaabaabaaaabaaa aaaabaaaabaabbbbbaabaaabaabaaaabaaa aaaabaaaabaabaaaaaabaaabaabaaaabaaa aaaabaaaabaabaaaaaabaaabaabaaabaaaa aaaabaaaabaaabbbbaabaaabaaabbbaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
16 進数のカラー コードとは別に、X11 のカラー名 (例: red
) を使用して色を指定することもできますNone
透明度を示します。
これは XPM 形式の現在のバージョンです。
1991 年にリリースされたバージョンでは C コードが復活しましたが、純粋な C コード スタイルを使用する代わりに、内部の値は基本的に XPM2 形式と同じです。
例:
/* XPM */ static char *_hello[] = { /* columns rows colors chars-per-pixel */ "35 25 2 1 ", " c white", "bc black", /* pixels */ " ", " ", " ", " ", " ", " ", " ", " bbb ", " bbb ", " bbb ", " b bbb bbb bb bbbb ", " bb bb bbbbb bb ", " bbbbbbbb ", " bb bbbbb bbbb ", " bbbbbbb ", " bbbbbbb ", " bb bbbb bb bbb ", " ", " ", " ", " ", " ", " ", " ", " " };
説明したように、XPM 形式は、JFrog ロゴなど、より洗練された画像を表すこともできます。
DoS 脆弱性 – CVE-2023-43786
CVE-2023-43786 の脆弱性は、本質的には、誤った再帰停止条件の計算によって生じる無限ループです。
修正されたコミット:
https://gitlab.freedesktop.org/xorg/lib/libx11/-/commit/204c3393c4c90a29ed6bef64e43849536e863a86
XPutImage
、X Drawable (通常は X Window) に画像を配置できるようにする libX11 の関数です。この関数を使用すると、ピクセル情報を XImage 構造体からウィンドウやピックスマップなどの指定されたドローアブルに転送し、必要に応じて配置できます。
xpmCreatePixmapFromImage
xpmCreatePixmapFromImage
libXpm 関数は、次のXPutImage
関数を呼び出します。
void xpmCreatePixmapFromImage( Display *display, Drawable d, XImage *ximage, Pixmap *pixmap_return) { GC gc; XGCValues values; *pixmap_return = XCreatePixmap(display, d, ximage->width, ximage->height, ximage->depth); /* set fg and bg in case we have an XYBitmap */ values.foreground = 1; values.background = 0; gc = XCreateGC(display, *pixmap_return, GCForeground | GCBackground, &values); XPutImage(display, *pixmap_return, gc, ximage, 0, 0, 0, 0, ximage->width, ximage->height); XFreeGC(display, gc); }
この関数では、 ximage
表示されるソース イメージのピクセル データであり、 X Drawable
オブジェクト (この場合はpixmap_return
) にコピーされます。
XPutImage
XPutImage
libX11 関数は次のとおりです。
int XPutImage ( register Display *dpy, Drawable d, GC gc, register XImage *image, int req_xoffset, int req_yoffset, int x, int y, unsigned int req_width, unsigned int req_height){ ..... PutSubImage(dpy, d, gc, &img, 0, 0, x, y, (unsigned int) width, (unsigned int) height, dest_bits_per_pixel, dest_scanline_pad); UnlockDisplay(dpy); SyncHandle(); Xfree(img.data); return 0; } }
LockDisplay(dpy); FlushGC(dpy, gc); PutSubImage(dpy, d, gc, image, req_xoffset, req_yoffset, x, y, (unsigned int) width, (unsigned int) height, dest_bits_per_pixel, dest_scanline_pad);......... }
PutSubImage
関数を呼び出します。
static void PutSubImage ( register Display *dpy, Drawable d, GC gc, register XImage *image, int req_xoffset, int req_yoffset, int x, int y, unsigned int req_width, unsigned int req_height, int dest_bits_per_pixel, int dest_scanline_pad) { int left_pad, BytesPerRow, Available; if ((req_width == 0) || (req_height == 0)) return; Available = ((65536 < dpy->max_request_size) ? (65536 << 2) : (dpy->max_request_size << 2)) - SIZEOF(xPutImageReq); if ((image->bits_per_pixel == 1) || (image->format != ZPixmap)) { [1] left_pad = (image->xoffset + req_xoffset) & (dpy->bitmap_unit - 1); BytesPerRow = (ROUNDUP((long)req_width + left_pad, dpy->bitmap_pad) >> 3) * image->depth; } else { [2] left_pad = 0; BytesPerRow = ROUNDUP((long)req_width * dest_bits_per_pixel, [3] dest_scanline_pad) >> 3; } if ((BytesPerRow * req_height) <= Available) { [4] PutImageRequest(dpy, d, gc, image, req_xoffset, req_yoffset, x, y, req_width, req_height, dest_bits_per_pixel, dest_scanline_pad); } else if (req_height > 1) { int SubImageHeight = Available / BytesPerRow; if (SubImageHeight == 0) SubImageHeight = 1; PutSubImage(dpy, d, gc, image, req_xoffset, req_yoffset, x, y, req_width, (unsigned int) SubImageHeight, dest_bits_per_pixel, dest_scanline_pad); PutSubImage(dpy, d, gc, image, req_xoffset, req_yoffset + SubImageHeight, x, y + SubImageHeight, req_width, req_height - SubImageHeight, dest_bits_per_pixel, dest_scanline_pad); } else { [5] int SubImageWidth = (((Available << 3) / dest_scanline_pad) [6] * dest_scanline_pad) - left_pad; PutSubImage(dpy, d, gc, image, req_xoffset, req_yoffset, x, y, (unsigned int) SubImageWidth, 1, dest_bits_per_pixel, dest_scanline_pad); PutSubImage(dpy, d, gc, image, req_xoffset + SubImageWidth, req_yoffset, x + SubImageWidth, y, req_width - SubImageWidth, 1, dest_bits_per_pixel, dest_scanline_pad); } }
次のサンプル画像を見てみましょう。
Available [the requested size] = (65,536 * 4) - 28 = 262,116 bits_per_pixel = 32 width = 90,000 pixels height = 1 pixel
画像のbits_per_pixel
は 32 であるため、[1] の条件ステートメントは通過せず、[2] で定義された代替コード ブロックに入る必要があります。
次に、[3] のBytesPerRow
を計算し、8 で割ります。この例では、BytesPerRow = 90000 * 32 / 8 = 360,0
この例では、[4] のチェックはパスしません。360000 は要求されたサイズ 262116 より小さくなく、要求された幅の 1 行を 1 つの要求に収めることができないためです。これにより、[5] のelse
が開始されます。 ]。
これにより、1 つのリクエストに含めることができるピクセル数が決まります。次に、 PutSubImage
関数への再帰呼び出しを開始してそのサブセットのみを渡し、その後に続く再帰呼び出しで行の残りの部分を管理します。必要に応じて、この残りの部分を追加の再帰呼び出しによってさらに分割することもできます。
ただし、[6] の計算ではピクセルあたりのビット数が考慮されておらず、再帰呼び出しにより 2096928 ビットではなく 2096928 ピクセルを送信するリクエストが作成されます。これは 1 回のリクエストに収まりきらないサイズです。
これにより、ピクセルのラインを分割しようとする無限ループが発生し、その結果、数値が大きすぎて適合しなくなり、同じ値でプロセスを再試行することになります。この再帰は、呼び出しスタックが使い果たされるまで継続します。
バグ修正により[6] の計算が変更され、 bits_per_pixel
が考慮されました。この例では、65529 ピクセルだけの送信を要求する再帰呼び出しが行われ、BytesPerRow が 262116 となり、利用可能なスペース内に完全に収まるため、再帰は前方に進み、わずか 2 回の呼び出しで終了することができます。
バグを引き起こす概念実証イメージの例: https://github.com/jfrog/jfrog-CVE-2023-43786-libX11_DoS/blob/main/cve-2023-43786.xpm
脆弱な libXpm ライブラリ関数を呼び出すアプリの例としては、Xpm イメージを画面に表示するために使用される CLI ユーティリティsxpm
があります。
これは、脆弱なxpmCreatePixmapFromImage
Xpm 関数を呼び出し、次に脆弱な libX11 関数XPutImage
を呼び出し、次にPutSubImage
。