From nobody Thu Jun 26 07:38:03 2025 X-Original-To: dev-commits-src-main@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4bSVsR6BcCz60CMq; Thu, 26 Jun 2025 07:38:03 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R10" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 4bSVsR5LXfz4Dfp; Thu, 26 Jun 2025 07:38:03 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1750923483; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=ykb1s8grkOftDvt0nSyY13dxgJ/42spYbr4R3H9l83U=; b=ppRqr7eF6c2vOPawFqg0VSGGhtmeJJfJZw40WRbiRWL3PlNvre0hPeLF0SCNLRlEuxOpMB Mx5OTncGegSTURboH9WyAMab4hMbqT2IlkWaUqOQdPR13bFLZZmCHYzPhNtEduka9Mh3av /zcO/g9NSoKO78y6bGW4XjNTsQvMWL3OylFfTxYe+nH8n6DAPEHGF9BTYSLFT7zeaN1qEY hQZLoLx3FSra2fmVWFQp46wAJX7aBTI3tIHfUvPDUzFaFvpBB1GO5mddWRRK2uweXwkzti +Bq4bjPZE5B4/GY43/RUgz/D7aNra1bG2dJ8fw6heAscVlBVEFLD2pbME/BP7g== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1750923483; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=ykb1s8grkOftDvt0nSyY13dxgJ/42spYbr4R3H9l83U=; b=Wa9Wqx8bL7qHA10jN/x5e5Mk/d7dON71dw8uq+I9Sb/B0MCMkkevR+/V9uIxaj95vn+I2Y beteIQbRGqQi7h+XQMM87YoF1vnB6ruaWs75V7gWexnwxZduVkmr3W6Qny3ezHVnlBhogi nu1sysLwQx39UTJlaeqd6ZFvw0eflWENz8U+IuEgQhfQslDofKSTh9n8sDN+FFXUJNODBA LbqbMHCi0T8L6HY/zjwtQ42LXspyjAnRSUqgDnnmsKJs4MX8/ieaX40CFuP0VkyWJ3bnBs lA7/qVpLmukhMaVxJ0FyKT/uSEjwuY3w0zvM9N4xFCH3+/NzO36I+KQY/qx+ig== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1750923483; a=rsa-sha256; cv=none; b=coCZeHtU5/4SaqoVzBmlIiFNz+ssc/SqknmTKx4PI6I/B9AcQ2p4uvCCVwXsgx38KCPv9y 1NhHbsuKcgj9coNWziBz83ZrJioU/cI7+JUwforgJy7E1MThoH7PEhrZj/1P9pxxjH/woO ONHHPB0QznpbtoYLD7r12ku+Sb3gtCiD0i+GyyS8qD3eIMX7ZSCznwHciwLszDbFl++yR6 eImAUzyR9Z8Ehz0fu4hg2ZQVR8nVmqVBdkoHlDalFXZPYEv9fB2cAzFjnuRAFiEw4K9IM1 rWO/p9Fmd4V7K4rCUq4HhZw4s+4HifAe65mty/3CNps1MMOKfiV1ExBsnzTmMg== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id 4bSVsR4g22zsnD; Thu, 26 Jun 2025 07:38:03 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from gitrepo.freebsd.org ([127.0.1.44]) by gitrepo.freebsd.org (8.18.1/8.18.1) with ESMTP id 55Q7c39J080164; Thu, 26 Jun 2025 07:38:03 GMT (envelope-from git@gitrepo.freebsd.org) Received: (from git@localhost) by gitrepo.freebsd.org (8.18.1/8.18.1/Submit) id 55Q7c3qG080161; Thu, 26 Jun 2025 07:38:03 GMT (envelope-from git) Date: Thu, 26 Jun 2025 07:38:03 GMT Message-Id: <202506260738.55Q7c3qG080161@gitrepo.freebsd.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Dag-Erling =?utf-8?Q?Sm=C3=B8rgrav?= Subject: git: 62e0f12f5104 - main - scandir: Propagate errors from readdir(). List-Id: Commit messages for the main branch of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-main List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-main@freebsd.org Sender: owner-dev-commits-src-main@FreeBSD.org MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: des X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: 62e0f12f5104585b7346fee183e5c667b39ddbad Auto-Submitted: auto-generated The branch main has been updated by des: URL: https://cgit.FreeBSD.org/src/commit/?id=62e0f12f5104585b7346fee183e5c667b39ddbad commit 62e0f12f5104585b7346fee183e5c667b39ddbad Author: Dag-Erling Smørgrav AuthorDate: 2025-06-26 07:37:00 +0000 Commit: Dag-Erling Smørgrav CommitDate: 2025-06-26 07:37:00 +0000 scandir: Propagate errors from readdir(). Currently, if `readdir()` fails, `scandir()` simply returns a partial result (or a null result if it fails before any entries were selected). There is no way within the current API design to return both a partial result and an error indicator, so err on the side of caution: if an error occurs, discard any partial result and return the error instead. MFC after: 1 week Reported by: Maxim Suhanov Sponsored by: Klara, Inc. Reviewed by: markj Differential Revision: https://reviews.freebsd.org/D51046 --- lib/libc/gen/scandir.3 | 32 +++++++++++++++++++- lib/libc/gen/scandir.c | 12 ++++++-- lib/libc/tests/gen/scandir_test.c | 63 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 3 deletions(-) diff --git a/lib/libc/gen/scandir.3 b/lib/libc/gen/scandir.3 index 9ced9fa4ef9d..3da4500cefb9 100644 --- a/lib/libc/gen/scandir.3 +++ b/lib/libc/gen/scandir.3 @@ -25,7 +25,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd June 20, 2025 +.Dd June 25, 2025 .Dt SCANDIR 3 .Os .Sh NAME @@ -215,6 +215,36 @@ cannot allocate enough memory to hold all the directory entries, they return \-1 and set .Va errno to an appropriate value. +.Sh ERRORS +The +.Fn scandir , +.Fn scandirat , +.Fn scandir_b , +and +.Fn scandirat_b +functions may fail and set +.Va errno +for any of the errors specified for the +.Xr opendir 3 , +.Xr malloc 3 , +.Xr readdir 3 , +and +.Xr closedir 3 +functions. +.Pp +The +.Fn fdscandir +and +.Fn fdscandir_b +functions may fail and set +.Va errno +for any of the errors specified for the +.Xr fdopendir 3 , +.Xr malloc 3 , +.Xr readdir 3 , +and +.Xr closedir 3 +functions. .Sh SEE ALSO .Xr openat 2 , .Xr directory 3 , diff --git a/lib/libc/gen/scandir.c b/lib/libc/gen/scandir.c index 134c88713d39..56d77c29bd07 100644 --- a/lib/libc/gen/scandir.c +++ b/lib/libc/gen/scandir.c @@ -81,7 +81,7 @@ scandir_dirp(DIR *dirp, struct dirent ***namelist, if (names == NULL) return (-1); - while ((d = readdir(dirp)) != NULL) { + while (errno = 0, (d = readdir(dirp)) != NULL) { if (select != NULL && !SELECT(d)) continue; /* just selected names */ /* @@ -108,6 +108,13 @@ scandir_dirp(DIR *dirp, struct dirent ***namelist, } names[numitems++] = p; } + /* + * Since we can't simultaneously return both -1 and a count, we + * must either suppress the error or discard the partial result. + * The latter seems the lesser of two evils. + */ + if (errno != 0) + goto fail; if (numitems && dcomp != NULL) #ifdef I_AM_SCANDIR_B qsort_b(names, numitems, sizeof(struct dirent *), (void*)dcomp); @@ -120,7 +127,8 @@ scandir_dirp(DIR *dirp, struct dirent ***namelist, fail: serrno = errno; - free(p); + if (numitems == 0 || names[numitems - 1] != p) + free(p); while (numitems > 0) free(names[--numitems]); free(names); diff --git a/lib/libc/tests/gen/scandir_test.c b/lib/libc/tests/gen/scandir_test.c index 9a9940aca881..f7b52b5e3616 100644 --- a/lib/libc/tests/gen/scandir_test.c +++ b/lib/libc/tests/gen/scandir_test.c @@ -7,7 +7,9 @@ #include #include +#include #include +#include #include #include @@ -124,11 +126,72 @@ ATF_TC_BODY(scandir_none, tc) free(namelist); } +/* + * Test that scandir() propagates errors from readdir(): we create a + * directory with enough entries that it can't be read in a single + * getdirentries() call, then abuse the selection callback to close the + * file descriptor scandir() is using after the first call, causing the + * next one to fail, and verify that readdir() returns an error instead of + * a partial result. We make two passes, one in which nothing was + * selected before the error occurred, and one in which everything was. + */ +static int scandir_error_count; +static int scandir_error_fd; +static int scandir_error_select_return; + +static int +scandir_error_select(const struct dirent *ent __unused) +{ + if (scandir_error_count++ == 0) + close(scandir_error_fd); + return (scandir_error_select_return); +} + +ATF_TC(scandir_error); +ATF_TC_HEAD(scandir_error, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Test that scandir() propagates errors from readdir()"); +} +ATF_TC_BODY(scandir_error, tc) +{ + char path[16]; + struct dirent **namelist = NULL; + int fd, i, ret; + + ATF_REQUIRE_EQ(0, mkdir("dir", 0755)); + for (i = 0; i < 1024; i++) { + snprintf(path, sizeof(path), "dir/%04x", i); + ATF_REQUIRE_EQ(0, symlink(path + 4, path)); + } + + /* first pass, select nothing */ + ATF_REQUIRE((fd = open("dir", O_DIRECTORY | O_RDONLY)) >= 0); + scandir_error_count = 0; + scandir_error_fd = fd; + scandir_error_select_return = 0; + ret = fdscandir(fd, &namelist, scandir_error_select, NULL); + ATF_CHECK_EQ(-1, ret); + ATF_CHECK_ERRNO(EBADF, ret < 0); + ATF_CHECK_EQ(NULL, namelist); + + /* second pass, select everything */ + ATF_REQUIRE((fd = open("dir", O_DIRECTORY | O_RDONLY)) >= 0); + scandir_error_count = 0; + scandir_error_fd = fd; + scandir_error_select_return = 1; + ret = fdscandir(fd, &namelist, scandir_error_select, NULL); + ATF_CHECK_EQ(-1, ret); + ATF_CHECK_ERRNO(EBADF, ret < 0); + ATF_CHECK_EQ(NULL, namelist); +} + ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, scandir_test); ATF_TP_ADD_TC(tp, fdscandir_test); ATF_TP_ADD_TC(tp, scandirat_test); ATF_TP_ADD_TC(tp, scandir_none); + ATF_TP_ADD_TC(tp, scandir_error); return (atf_no_error()); }