パスワードを忘れた? アカウント作成
16396784 story
バグ

m68k用Linuxに実装されたstrcmp()のコードにバグ。12年間気がつかず 53

ストーリー by nagazou
使用頻度 部門より

Linuxに実装されているMotorola MC68000(m68k)アーキテクチャ向けに最適化された文字列比較に使用される「strcmp」関数の手書きのアセンブリコードが、2010年10月の実装時から2022年12月まで12年間の間、正常に動作していなかったことが判明した。Linus Torvalds氏はこの件に関して「常に壊れていた」と発言している(PhoronixLinus Torvalds氏による説明)。

この問題についての詳細をツイートしているFadis氏の説明を引用すると、

Linuxのm68kサポートには2010年にアセンブリで書かれた高速なstrcmpの実装が追加された。しかしこの実装は2つの文字を8bit整数のまま減算して結果が0でなければそれを返り値とするという実装だった為、非ASCII文字を含む場合に正しくない結果を返す不具合があった

とのこと。

この議論は賞味期限が切れたので、アーカイブ化されています。 新たにコメントを付けることはできません。
  • まあ、当事は、非Ascii な文字は事実上存在無かったということなのでしょうけど。

    strcmp() の返値は、「文字列の大小」を反映した、負・ゼロ・正なんですが、そもそも、非Ascii文字列の「大小」なんて、統一的な定義は難しそうなので(あるのかな?)m68kの処理系では、これはバグではなく仕様である~にならないのかなぁ?
    --
    ¶「だますのなら、最後までだまさなきゃね」/ 罵声に包まれて、君はほほえむ。
    • #4387599 を読んだ。
      納得した。
      ……と思ったけど、これだと(負の値が決して返らない)だと、ASCIIでもおかしくなるんではないかなと、ふと思った。
      --
      ¶「だますのなら、最後までだまさなきゃね」/ 罵声に包まれて、君はほほえむ。
      親コメント
      • by Anonymous Coward

        当時のコンパイラはcharのデフォルトがsignedだったとかでしょう。
        多言語対応でcharのデフォルトをunsignedにした処理系では正しく動かなくなる。

    • by ogino (1668) on 2022年12月28日 17時18分 (#4387640) 日記

      > まあ、当事は、非Ascii な文字は事実上存在無かったということなのでしょうけど。

      カーネル内には無かったのでしょうが、一般的には m68k の時代に ISO-8859-1(Latin-1) が無かったはずは無いかと。

      C言語的に大小は定義されていないのかもしれませんが、人間的には ASCII の A (0x41=65) と Latin-1 アキュート・アクセント付きの A (0xC1) を比較したら、A < Á を期待するように思います。しかし 0xC1 を 193 でなく -63 と扱うと逆転する、と。

      # ヌル文字依存の関数を使っているのは良いのか、という気もしました。

      親コメント
  • by poly (42427) on 2022年12月28日 15時44分 (#4387591) 日記
    strcmp()をそのまま使ってたのではなかったのか…
    • by Anonymous Coward on 2022年12月28日 15時48分 (#4387594)

      カーネルは普通のlibcは使わんでしょうね

      親コメント
      • by poly (42427) on 2022年12月28日 16時02分 (#4387602) 日記
        カーネル用にstatic link版を別途用意しないといけないとは云え、使いまわせるとこは使いまわした方が保守が楽だと思うけど、そういう訳にもいかない事情がありそうですね。
        親コメント
        • by Anonymous Coward

          カーネル用途は求められるものが全然違うし開発してる人達も違うので無理矢理使い回す手間が割に合わんでしょ。

      • by Anonymous Coward

        コードをコピーしてたとか?

  • by Anonymous Coward on 2022年12月28日 17時59分 (#4387663)

    2000年頃、PPC用のglibcでも
    8ビット文字列を比較すると正しい結果を返さない
    アセンブリコードで組まれたstrcmpの問題がありましたね

  • by Anonymous Coward on 2022年12月28日 15時07分 (#4387565)

    まあ68kでバリバリ非ASCII文字使うことないだろうからまあいいんじゃないっすかね。

    • by Anonymous Coward on 2022年12月28日 15時29分 (#4387581)

      strcmp()は2つの機能があって
      - 文字列の一致不一致の判定
      - 大小の比較
      ができるけど、後者にバグがあったってことですね。

      バグが出る条件は
      - m68k
      - 不一致の文字が非ASCIII文字の場合
      で、 カーネル内部では大小の比較なんてめったに使わないし、そもそも m68k なんて誰も使ってない。

      12年間も誰も気が付かないというか、12年間だれもつかってなかった、ってところでしょうね。

      親コメント
      • by Anonymous Coward on 2022年12月28日 15時55分 (#4387598)

        親コメントが「ASCIII」になっていることも
        ここで指摘しておかなければ誰も気づかないかもしれない

        親コメント
      • by Anonymous Coward

        どのみち、非ASCII文字がコード順でソートされても大した意味ないですからね。
        JISなら多少は意味あったけどUnicode系だとね...

        • by Anonymous Coward

          おっと、漢数字の悪口はそこまでだ!

      • by Anonymous Coward

        複雑な挙動のテストケース漏れは仕方ないと思うけれど、こんな標準関数レベルですらテストコードが不十分だったんでしょうか。
        このくらい基本的なテストコードなら、アセンブラでもアーキテクチャ依存でもなく共通で使いまわしができると思うのですが。

        • by Anonymous Coward on 2022年12月28日 15時56分 (#4387599)

          元記事を読んでみた。

          馬具は、アセンブラじゃなくて、asmを呼び出してるCのコードとの兼ね合い。
          アセンブラは正しく実装されていて結果を符号付き8-bitで返している。なので、
          負の値を返すには(カーネル内の問題となったstrcmpの戻り値の型がintだから)符号拡張しなければならんのだが、Cのコードは、このレジスタをcharで受けている。
          charとだけ書くと、unsigned かsignedか決まっていなくて、処理系ごとにどちらかに決まる。
          m68kで使っていたコンパイラはsigned扱いだったのね。なので、結果の8-bit目が1だったら、符号拡張されて、負の値がちゃんと返っていた。
          そこに、コンパイルオプション-funsigned-charを追加したものだから、charはunsigned charと解釈され、char->ssize_tの変換で符号拡張しなくなって、負の値を返さなきゃならないときにも正の値が返るようになってバグが覚醒したと。

          親コメント
          • by Anonymous Coward

            見方によっちゃコードのバグではなくビルド設定のミスだな……
            signed charとして扱う責任をビルド設定に持たすか各コードに持たすか

            charの符号有無とかビルド設定切り替えるときは影響範囲確認せぇやって話だな

            個人的には符号なしはbyte的な形で扱えば済むからcharは符号付きのが楽な気がしないでもない
            でもis系関数相手だと符号無しのが楽よな……うーん

        • このくらい基本的なテストコードなら、アセンブラでもアーキテクチャ依存でもなく共通で使いまわしができると思うのですが。

          速度を稼ぐための「(m68k)アーキテクチャ向けに最適化された…アセンブリコード」から、標準のstrcmpを使うように修正した、という話ではないでしょうか。

          int strcmp() に対して、明示型キャストなしで char res を return していたのが、to make 'char' be unsigned in the kernel across all architectures(commit 3bc753c06dd0: "kbuild: treat char as always unsigned"). でまずいことに気がつかれた、と読めます。

          親コメント
          • by Anonymous Coward

            実装がアセンブリだろうがCだとうが、strcmpをテストするコードはCで汎用的に書けるんだからx86もarmもm68kも同じテストコード使えるでしょ。

            であれば、1回まともなテストコードを書いてテストしてたら真っ先に見つけられていたはずで
            それすらできてなかったの?ってツッコミでしょ。

            • by Anonymous Coward

              後からなら何とでも言えるの典型

              • by Anonymous Coward

                標準関数にテストケースが無かったのかなんて、こんな珍事でもなきゃ話題にしようがないだろ

              • by Anonymous Coward

                それもあるが、「完全に思い込み」で書いているのが滲み出ている。読解力が無いのか、理解力が無いのか、時間が無いのか、知らんが、少し注意して読めば分かることを見逃している。
                こういう方は、わかったつもりになって威張り散らす困った人になっていることに気づいてほしい。(昔の自分を見ているようで恥ずかしい。)

              • by Anonymous Coward

                #4387590です。
                自分の書き方が拙くて勘違いさせてしまったようです。私の意図は、#4387690の方の指摘のとおりです。
                でも、#4387599の方の技術的コメントが頂けたので恥を晒した分以上のリターンはありました。

                #馬具という殆ど使わない単語の誤変換はどうして起きたんだろう?

      • by Anonymous Coward

        memcmpの方は大丈夫だったのかなあ
        ASCII文字という先入観がない分、きちんとできたのかな

        • by Anonymous Coward

          str系の引数はconst char*だから引きずられてcharを使いがちだけど
          mem系の引数はconst void*で必ず思考を強いられるからまずintかBYTEになるだろうね

          --
          ¶「だますのなら、最後までだまさなきゃね」/ 罵声に包まれて、君はほほえむ。

  • by Anonymous Coward on 2022年12月28日 15時09分 (#4387566)

    実装を変更する前に、テストプログラムをm68k以外と共通化すべきでは?実装の変更はその次で。

  • by Anonymous Coward on 2022年12月28日 15時45分 (#4387592)

    まあLinux/m68kポートなんて生涯使うことないだろうからどうでもいいけど
    もっといえばstrcmp自体最後に使ったのいつだった?って状態なのに、libcのstrcmpでさえなく、カーネル組み込みのstrcmpの方の話って…
    どうでもよすぎて笑えてくる

    • by Anonymous Coward

      どちらかというと、よく気がついたなと思います。

      #記事のリンク先の"Linusの説明"にある修正パッチが割り切っていて素敵。

  • by Anonymous Coward on 2022年12月28日 16時12分 (#4387609)

    #ifdef CP932のときはこういう風に1文字目2文字目判定して~って感じで言語を羅列しまくるのがいいの?

    • by Anonymous Coward

      https://srad.jp/comment/4387599 [srad.jp]
      を読めばいい。
      アセンブラのコードに間違いはないが
      C側の符号符号判定の部分でバグが発生するとのこと。

  • by Anonymous Coward on 2022年12月28日 16時15分 (#4387611)

    Cのis-()系に(signed) charをそのまま渡している人は結構いそう。
    # EOF以外の負の値を渡すのは未定義動作ですよ。

    LinuxカーネルはCの規格は関係ないとは言え、GCCのマニュアルを読んでなかったことがあっただろ。

  • by Anonymous Coward on 2022年12月28日 17時23分 (#4387642)

    カーネルってgcc使ってても自分自身で使う標準ライブラリは自分で用意するの?

    • by Anonymous Coward

      カーネルはすくなくともCのホスト環境では書かれていないので、string.h相当がなかったら自分で用意する必要がある。

      ふつうにCと言ったらホスト環境でしょう。

    • by Anonymous Coward

      カーネルモードでユーザーモードのライブラリをそのまま使えないのは常識だが? WindowsもmsvcrtやUCRTを使えないから独自のRTL用意されているぞ。
      たとえばユーザーモードでの例外やシグナルはカーネルがいい感じに処理してくれることを前提に書かれている。それをカーネルモードでそのまま使ったらどうなると思う?

    • by Anonymous Coward

      一番簡単な例で言うと標準Cライブラリのmalloc()やfree()はカーネルにお願いしてメモリを取ってきたり解放したりすることになる。
      ゆえにカーネルは標準ライブラリは呼べない。

  • by Anonymous Coward on 2022年12月28日 17時31分 (#4387647)

    派生したやつはこのライブラリ使ってるのかな
    販売しているシステムorチップで現役の68kのものってありますかね
    大分ARMとかに駆逐された感。
    駆逐されたのは68kだけじゃないけど

    • by Anonymous Coward

      これ [zuiki.co.jp]は?

      # 改めてLinuxカーネルをmakeすることはないかな

      • by Anonymous Coward

        ここんち、ARMのSoC屋さんだけど
        「X68000 Z」ってARMでのエミュレーションじゃないの?
        # それとも、単に「その68kじゃない」、といえばいいのかな

  • by Anonymous Coward on 2022年12月28日 19時02分 (#4387711)

    68Kといいつつ対象は68020以降とか?

    • by Anonymous Coward

      linuxでもMMU無しのサブセット版があって、それが動くはず。
      使ったことないんだけど。

    • by Anonymous Coward

      まあ、x86といいつつ対象は80386以降だったUNIX互換OSもあったらしいし、ね。

      • by Anonymous Coward

        今は実用上、x86といったら普通は80386以降のことだと思うけど
        68Kという呼び方は68000のことで、その他を指す用法はちょっと聞いたことがないなあ。

        • by Anonymous Coward

          最初のバージョンの話をしてるのになんで突然「今は」が出てくるの? 「今は」386どころか486のサポートもやめようとか話してるよ

          • by Anonymous Coward

            m68kは文字を素直に解釈するとMC68000のことだが、LinuxではMC68020+MC68851以上を指す。
            同様にx86という呼び方は、i486が出た後にi386以上を一括りにするために生まれたが、
            文字を素直に解釈するとi80186やi80286が含まれるので、正確な表現じゃない。
            ということを、#4387831のコメントは言いたいんだと思う。

typodupeerror

あつくて寝られない時はhackしろ! 386BSD(98)はそうやってつくられましたよ? -- あるハッカー

読み込み中...