strlcpy/strlcat 終究還是沒能加入 glibc

四 05 五月 2022 by ols3

這算是程式語言考古吧! ;-)

儘管許多人曾倡議 glibc 應加入 strlcpy/strlcat,以避免日益嚴重的 buffer overflow 攻擊,但是這個二十幾年來的期望,終究沒有實現;至今,glibc 仍把這兩個函式排除在外!

(到底 glibc 是有多厭惡 strlcpy/strlcat? ;-) )

請參閱 glibc 的 FAQ: Why no strlcpy / strlcat?

glibc 所持的理由是:這兩個函式沒有辦法完全避免目標區出現緩衝區溢位,而且,字串可能會被無聲無息地截斷,不但增加複雜度,而且效率低落,因此,glibc 最終還是沒有接受 strlcpy/strlcat。

glibc 認為不必修改程式碼,使用 gcc -D_FORTIFY_SOURCE 一樣可以抓出問題;若不考慮效能,snprintf 函式也能做到,那加入 strlcpy/strncpy 作啥?

奇怪的是,在 BSD 族群中(OpenBSD/FreeBSD...),strlcpy/strlcat 卻是基本配備,尤其 OpenBSD 更把 strlcpy/strlcat 當成是他們加強程式碼安全的新技術,兩個陣營的思維可謂截然不同。

由於 glibc 沒有 strlcpy/strlcat,在 Linux 中若想使用這兩個函式,只能另外安裝 libbsd 或 libbsd-dev:

sudo apt install libbsd-dev

test-strlcpy.c:

#include <bsd/bsd.h>
#include <stdio.h>
int main()
{
  char buff[5];
  char src[10] = "abcdefgh";
  strlcpy(buff, src, sizeof(buff));
  printf("%s\n", buff); 
  return 0;
}

編譯執行:

gcc test-strlcpy.c -lbsd
./a.out

執行結果:

abcd

我們來看看 libbsd-dev 中的 strlcpy.c 如何實作:

#include <sys/types.h>
#include <string.h>

/*
 * Copy string src to buffer dst of size dsize.  At most dsize-1
 * chars will be copied.  Always NUL terminates (unless dsize == 0).
 * Returns strlen(src); if retval >= dsize, truncation occurred.
 */
size_t
strlcpy(char *dst, const char *src, size_t dsize)
{
    const char *osrc = src;
    size_t nleft = dsize;

    /* Copy as many bytes as will fit. */
    if (nleft != 0) {
        while (--nleft != 0) {
            if ((*dst++ = *src++) == '\0')
                break;
        }
    }

    /* Not enough room in dst, add NUL and traverse rest of src. */
    if (nleft == 0) {
        if (dsize != 0)
            *dst = '\0';        /* NUL-terminate dst */
        while (*src++)
            ;
    }

    return(src - osrc - 1); /* count does not include NUL */
}

其實和 OpenBSD 的 strlcpy.c 是一樣的:

/*  $OpenBSD: strlcpy.c,v 1.16 2019/01/25 00:19:25 millert Exp $    */

#include <sys/types.h>
#include <string.h>

/*
 * Copy string src to buffer dst of size dsize.  At most dsize-1
 * chars will be copied.  Always NUL terminates (unless dsize == 0).
 * Returns strlen(src); if retval >= dsize, truncation occurred.
 */
size_t
strlcpy(char *dst, const char *src, size_t dsize)
{
    const char *osrc = src;
    size_t nleft = dsize;

    /* Copy as many bytes as will fit. */
    if (nleft != 0) {
        while (--nleft != 0) {
            if ((*dst++ = *src++) == '\0')
                break;
        }
    }

    /* Not enough room in dst, add NUL and traverse rest of src. */
    if (nleft == 0) {
        if (dsize != 0)
            *dst = '\0';        /* NUL-terminate dst */
        while (*src++)
            ;
    }

    return(src - osrc - 1); /* count does not include NUL */
}
DEF_WEAK(strlcpy);

您支持哪一派?

其實我支持 BSD,看看 OpenBSD 的安全成就: Only two remote holes in the default install, in a heck of a long time!,就知道了,或許 OpenBSD 是對的! ;-)