簡體   English   中英

為什么我的(重新)strlen實現錯誤?

[英]Why is my (re)implementation of strlen wrong?

我想出了這個小代碼,但所有專業人士都表示它很危險,我不應該寫這樣的代碼。 任何人都可以在“更多”細節中突出其漏洞嗎?

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);
}

危險它有點延伸,但它是不必要的遞歸,並且可能比迭代替代方案效率低。

我想還有一個非常長的字符串,存在堆棧溢出的危險。

此代碼中存在兩個嚴重的安全漏洞:

  1. 使用int而不是size_t作為返回類型。 如上所述,長於INT_MAX字符串將導致此函數通過整數溢出調用未定義的行為。 在實踐中,這可能導致計算strlen(huge_string)為一些小值,如1, malloc '錯誤的內存量,然后執行strcpy ,導致緩沖區溢出。

  2. 無限遞歸,可以溢出堆棧,即堆棧溢出。 :-)編譯器可以選擇優化遞歸到循環(在這種情況下,它可以使用當前的編譯器技術),但不能保證它會。 在最好的情況下,堆棧溢出只會使程序崩潰。 在最壞的情況下(例如,在沒有保護頁面的線程上運行),它可能會破壞不相關的內存,可能會產生任意代碼執行。

殺死已經指出的堆棧的問題應該由一個體面的編譯器修復,其中明顯的遞歸調用被平坦化為循環。 我驗證了這個假設,並要求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 )是的,這段代碼是完全安全的,帶有合適標志的合適的編譯器可以解決遞歸問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM