cyan's blog

しょーもない事しか書いていません

__chk_range_not_ok() in Linux x86

Linux(tag v5.4)のソースコードリーディングをしていたときに面白いと思った関数のメモ.

__chk_range_not_ok()

arch/x86/include/asm/uaccess.hにて以下のように定義される.

static inline bool __chk_range_not_ok(unsigned long addr, unsigned long size, unsigned long limit)
{
        /*
         * If we have used "sizeof()" for the size,
         * we know it won't overflow the limit (but
         * it might overflow the 'addr', so it's
         * important to subtract the size from the
         * limit, not add it to the address).
         */
        if (__builtin_constant_p(size))  [1]
                return unlikely(addr > limit - size); [2]

        /* Arbitrary sizes? Be careful about overflow */
        addr += size; [3]
        if (unlikely(addr < size))
                return true;
        return unlikely(addr > limit);
}

この関数の動作は符号なし整数型のサイズを無視して記述すればreturn addr + size > limitである. しかし,Cコードの世界ではwrap aroundを考慮して記述しなければならない.(addr + sizeがwrap aroundする可能性がある)

愚直に実装するのであれば[3]以下のようにaddrにsizeを加算しwrap aroundをチェック. wrap aroundすれば0を返す.wrap aroundしなければreturn addrにsizeを加算した値 > limitすればよい.

この関数ではコンパイラの組み込み関数である__builtin_constant_p()を用いることで関数の高速化を図っている[1]. __builtin_constant_p()は引数に数値を取り,引数がコンパイル時に定数であること(constant-flodingされた場合なども含む)を証明できれば1,出来なければ0を返す.(戻り値0は定数でないことを意味しない,定数と証明できないことを意味する) その場合はsize > limitとならないように実装されているため,limit - sizeはwrap aroundを起こさない. よって[2]のように比較を行うことが可能である. 定数であることを判別できない場合はオーソドックスにwrap aroundのチェックを行ってから比較を行う.