私はこの小さなコードを思いつきましたが、すべての専門家は危険だと言っており、このようなコードを書くべきではありません. 「詳細」でその脆弱性を強調できる人はいますか?
int strlen(char *s){
return (*s) ? 1 + strlen(s + 1) : 0;
}
それ自体に脆弱性はありません。これは完全に正しいコードです。もちろん、それは時期尚早に悲観化されています。最短の文字列以外ではスタック スペースが不足し、再帰呼び出しのためにパフォーマンスが低下しますが、それ以外は問題ありません。
テール コールの最適化では、そのようなコードに対応できない可能性が高くなります。危険な生活を送り、テールコールの最適化に依存したい場合は、テールコールを使用するように言い換える必要があります。
// note: size_t is an unsigned integertype
int strlen_impl(const char *s, size_t len) {
if (*s == 0) return len;
if (len + 1 < len) return len; // protect from overflows
return strlen_impl(s+1, len+1);
}
int strlen(const char *s) {
return strlen_impl(s, 0);
}
少しばかり危険ですが、不必要に再帰的であり、反復的な代替方法よりも効率が悪い可能性があります。
また、非常に長い文字列を指定すると、スタック オーバーフローの危険性があると思います。
このコードには重大なセキュリティ バグが 2 つあります。
戻り値の型のint
代わりに使用します。size_t
書かれているように、より長い文字列INT_MAX
は、この関数が整数オーバーフローを介して未定義の動作を引き起こす原因となります。実際には、これはstrlen(huge_string)
1 のような小さな値として計算しmalloc
、間違った量のメモリを使用strcpy
して実行し、バッファ オーバーフローを引き起こす可能性があります。
スタックをオーバーフローさせる無制限の再帰、つまりスタック オーバーフロー。:-) コンパイラは再帰をループに最適化することを選択するかもしれません(この場合、現在のコンパイラ技術で可能です) が、そうなる保証はありません。最良のケースでは、スタック オーバーフローは単にプログラムをクラッシュさせるだけです。最悪の場合 (たとえば、ガード ページのないスレッドで実行されている場合)、無関係なメモリが破壊され、任意のコードが実行される可能性があります。
指摘されているスタックの強制終了の問題は、見かけ上の再帰呼び出しがループにフラット化される適切なコンパイラによって修正されるはずです。この仮説を検証し、clang にコードの翻訳を依頼しました。
//sl.c
unsigned sl(char const* s) {
return (*s) ? (1+sl(s+1)) : 0;
}
コンパイルと逆アセンブル:
clang -emit-llvm -O1 -c sl.c -o sl.o
# ^^ Yes, O1 is already sufficient.
llvm-dis-3.2 sl.o
そして、これは llvm 結果 (sl.o.ll) の関連部分です。
define i32 @sl(i8* nocapture %s) nounwind uwtable readonly {
%1 = load i8* %s, align 1, !tbaa !0
%2 = icmp eq i8 %1, 0
br i1 %2, label %tailrecurse._crit_edge, label %tailrecurse
tailrecurse: ; preds = %tailrecurse, %0
%s.tr3 = phi i8* [ %3, %tailrecurse ], [ %s, %0 ]
%accumulator.tr2 = phi i32 [ %4, %tailrecurse ], [ 0, %0 ]
%3 = getelementptr inbounds i8* %s.tr3, i64 1
%4 = add i32 %accumulator.tr2, 1
%5 = load i8* %3, align 1, !tbaa !0
%6 = icmp eq i8 %5, 0
br i1 %6, label %tailrecurse._crit_edge, label %tailrecurse
tailrecurse._crit_edge: ; preds = %tailrecurse, %0
%accumulator.tr.lcssa = phi i32 [ 0, %0 ], [ %4, %tailrecurse ]
ret i32 %accumulator.tr.lcssa
}
再帰呼び出しが表示されません。実際、clang はループ ラベルtailrecurse
を呼び出し、clang がここで何をしているかについてのポインタを提供します。
したがって、最後に ( tl;dr ) はい、このコードは完全に安全であり、適切なフラグを備えた適切なコンパイラは再帰を解決します。