The branch main has been updated by bnovkov:

URL: 
https://cgit.FreeBSD.org/src/commit/?id=14598537acecb512432e33e001762c912cc25799

commit 14598537acecb512432e33e001762c912cc25799
Author:     Bojan Novković <bnov...@freebsd.org>
AuthorDate: 2025-07-25 09:01:52 +0000
Commit:     Bojan Novković <bnov...@freebsd.org>
CommitDate: 2025-07-30 09:31:16 +0000

    db/hash.c: Allow O_WRONLY in dbm_open
    
    The dbm(3) manpage explicitly states that O_WRONLY is not allowed in
    dbm_open, but a more recent comment in ` __hash_open` suggests otherwise.
    Furthermore, POSIX.1 allows O_WRONLY in dbm_open and states
    that the underlying file must be opened for both reading and writing.
    
    Fix this by correcting the O_WRONLY check and moving it further into
    the function to make sure that the original flags are stored in hashp.
    
    Sponsored by:   Klara, Inc.
    Reviewed by:    markj
    Differential Revision:  https://reviews.freebsd.org/D51514
---
 lib/libc/db/hash/hash.c           | 15 +++---
 lib/libc/db/man/dbm.3             |  5 +-
 lib/libc/db/man/dbopen.3          |  5 +-
 lib/libc/tests/db/Makefile        |  1 +
 lib/libc/tests/db/dbm_open_test.c | 23 ++++++---
 lib/libc/tests/db/dbm_perm_test.c | 98 +++++++++++++++++++++++++++++++++++++++
 6 files changed, 126 insertions(+), 21 deletions(-)

diff --git a/lib/libc/db/hash/hash.c b/lib/libc/db/hash/hash.c
index cc96fb5ce326..1eb01ee0f0c5 100644
--- a/lib/libc/db/hash/hash.c
+++ b/lib/libc/db/hash/hash.c
@@ -99,11 +99,6 @@ __hash_open(const char *file, int flags, int mode,
        DB *dbp;
        int bpages, hdrsize, new_table, nsegs, save_errno;
 
-       if ((flags & O_ACCMODE) == O_WRONLY) {
-               errno = EINVAL;
-               return (NULL);
-       }
-
        if (!(hashp = (HTAB *)calloc(1, sizeof(HTAB))))
                return (NULL);
        hashp->fp = -1;
@@ -115,6 +110,10 @@ __hash_open(const char *file, int flags, int mode,
         * we can check accesses.
         */
        hashp->flags = flags;
+       if ((flags & O_ACCMODE) == O_WRONLY) {
+               flags &= ~O_WRONLY;
+               flags |= O_RDWR;
+       }
 
        if (file) {
                if ((hashp->fp = _open(file, flags | O_CLOEXEC, mode)) == -1)
@@ -180,7 +179,7 @@ __hash_open(const char *file, int flags, int mode,
                __buf_init(hashp, DEF_BUFSIZE);
 
        hashp->new_file = new_table;
-       hashp->save_file = file && (hashp->flags & O_RDWR);
+       hashp->save_file = file && (flags & O_RDWR);
        hashp->cbucket = -1;
        if (!(dbp = (DB *)malloc(sizeof(DB)))) {
                save_errno = errno;
@@ -524,6 +523,10 @@ hash_get(const DB *dbp, const DBT *key, DBT *data, 
u_int32_t flag)
                hashp->error = errno = EINVAL;
                return (ERROR);
        }
+       if ((hashp->flags & O_ACCMODE) == O_WRONLY) {
+               hashp->error = errno = EPERM;
+               return (ERROR);
+       }
        return (hash_access(hashp, HASH_GET, (DBT *)key, data));
 }
 
diff --git a/lib/libc/db/man/dbm.3 b/lib/libc/db/man/dbm.3
index c5a83c7acef4..30787600ad2d 100644
--- a/lib/libc/db/man/dbm.3
+++ b/lib/libc/db/man/dbm.3
@@ -13,7 +13,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd April 2, 2022
+.Dd July 25, 2025
 .Dt DBM 3
 .Os
 .Sh NAME
@@ -99,9 +99,6 @@ is a typical value for
 .Li 0660
 is a typical value for
 .Fa mode .
-.Dv O_WRONLY
-is not allowed in
-.Fa flags .
 The pointer returned by
 .Fn dbm_open
 identifies the database and is the
diff --git a/lib/libc/db/man/dbopen.3 b/lib/libc/db/man/dbopen.3
index 64cef88506d8..7fe515f17849 100644
--- a/lib/libc/db/man/dbopen.3
+++ b/lib/libc/db/man/dbopen.3
@@ -76,13 +76,10 @@ are as specified to the
 .Xr open 2
 routine, however, only the
 .Dv O_CREAT , O_EXCL , O_EXLOCK , O_NOFOLLOW , O_NONBLOCK ,
-.Dv O_RDONLY , O_RDWR , O_SHLOCK , O_SYNC
+.Dv O_RDONLY , O_RDWR , O_SHLOCK , O_SYNC, O_WRONLY,
 and
 .Dv O_TRUNC
 flags are meaningful.
-(Note, opening a database file
-.Dv O_WRONLY
-is not possible.)
 .\"Three additional options may be specified by
 .\".Em or Ns 'ing
 .\"them into the
diff --git a/lib/libc/tests/db/Makefile b/lib/libc/tests/db/Makefile
index 54b38b94a581..cc181cc81160 100644
--- a/lib/libc/tests/db/Makefile
+++ b/lib/libc/tests/db/Makefile
@@ -8,6 +8,7 @@ PROGS+=         h_lfsr
 ${PACKAGE}FILES+=              README
 
 ATF_TESTS_C+=          dbm_open_test
+ATF_TESTS_C+=          dbm_perm_test
 
 NETBSD_ATF_TESTS_C+=   db_hash_seq_test
 NETBSD_ATF_TESTS_SH+=  db_test
diff --git a/lib/libc/tests/db/dbm_open_test.c 
b/lib/libc/tests/db/dbm_open_test.c
index 18d398e16b2a..8a3e888bf72c 100644
--- a/lib/libc/tests/db/dbm_open_test.c
+++ b/lib/libc/tests/db/dbm_open_test.c
@@ -4,14 +4,15 @@
  * SPDX-License-Identifier: BSD-2-Clause
  */
 
-#include <sys/mman.h>
-
 #include <fcntl.h>
 #include <ndbm.h>
 #include <stdio.h>
 
 #include <atf-c.h>
 
+static const char *path = "tmp";
+static const char *dbname = "tmp.db";
+
 ATF_TC(dbm_open_missing_test);
 ATF_TC_HEAD(dbm_open_missing_test, tc)
 {
@@ -21,23 +22,31 @@ ATF_TC_HEAD(dbm_open_missing_test, tc)
 
 ATF_TC_BODY(dbm_open_missing_test, tc)
 {
-       const char *path = "tmp";
-       const char *dbname = "tmp.db";
 
        /*
         * POSIX.1 specifies that a missing database file should
         * always get created if O_CREAT is present, except when
         * O_EXCL is specified as well.
         */
-       ATF_CHECK(dbm_open(path, O_RDONLY, _PROT_ALL) == NULL);
+       ATF_CHECK(dbm_open(path, O_RDONLY, 0755) == NULL);
+       ATF_REQUIRE(!atf_utils_file_exists(dbname));
+       ATF_CHECK(dbm_open(path, O_RDONLY | O_CREAT, 0755) != NULL);
+       ATF_REQUIRE(atf_utils_file_exists(dbname));
+       ATF_CHECK(dbm_open(path, O_RDONLY | O_CREAT | O_EXCL, 0755) == NULL);
+}
+
+ATF_TC_WITHOUT_HEAD(dbm_open_wronly_test);
+ATF_TC_BODY(dbm_open_wronly_test, tc)
+{
+       ATF_CHECK(dbm_open(path, O_WRONLY, 0755) == NULL);
        ATF_REQUIRE(!atf_utils_file_exists(dbname));
-       ATF_CHECK(dbm_open(path, O_RDONLY | O_CREAT, _PROT_ALL) != NULL);
+       ATF_CHECK(dbm_open(path, O_WRONLY | O_CREAT, 0755) != NULL);
        ATF_REQUIRE(atf_utils_file_exists(dbname));
-       ATF_CHECK(dbm_open(path, O_RDONLY | O_CREAT | O_EXCL, _PROT_ALL) == 
NULL);
 }
 
 ATF_TP_ADD_TCS(tp)
 {
        ATF_TP_ADD_TC(tp, dbm_open_missing_test);
+       ATF_TP_ADD_TC(tp, dbm_open_wronly_test);
        return (atf_no_error());
 }
diff --git a/lib/libc/tests/db/dbm_perm_test.c 
b/lib/libc/tests/db/dbm_perm_test.c
new file mode 100644
index 000000000000..c07210292014
--- /dev/null
+++ b/lib/libc/tests/db/dbm_perm_test.c
@@ -0,0 +1,98 @@
+/*-
+ * Copyright (c) 2025 Klara, Inc.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <ndbm.h>
+#include <stdio.h>
+
+#include <atf-c.h>
+
+static const char *path = "tmp";
+static const char *dbname = "tmp.db";
+
+static void
+create_db(void)
+{
+       DB *db;
+       datum data, key;
+
+       data.dptr = "bar";
+       data.dsize = strlen("bar");
+       key.dptr = "foo";
+       key.dsize = strlen("foo");
+
+       db = dbm_open(path, O_RDWR | O_CREAT, 0755);
+       ATF_CHECK(db != NULL);
+       ATF_REQUIRE(atf_utils_file_exists(dbname));
+       ATF_REQUIRE(dbm_store(db, key, data, DBM_INSERT) != -1);
+       dbm_close(db);
+}
+
+ATF_TC_WITHOUT_HEAD(dbm_rdonly_test);
+ATF_TC_BODY(dbm_rdonly_test, tc)
+{
+       DB *db;
+       datum data, key;
+
+       bzero(&data, sizeof(data));
+       key.dptr = "foo";
+       key.dsize = strlen("foo");
+       create_db();
+
+       db = dbm_open(path, O_RDONLY, 0755);
+       data = dbm_fetch(db, key);
+       ATF_REQUIRE(data.dptr != NULL);
+       ATF_REQUIRE(strncmp((const char*)data.dptr, "bar", data.dsize) == 0);
+       ATF_REQUIRE(dbm_store(db, key, data, DBM_REPLACE) == -1);
+       ATF_REQUIRE(errno == EPERM);
+}
+
+ATF_TC_WITHOUT_HEAD(dbm_wronly_test);
+ATF_TC_BODY(dbm_wronly_test, tc)
+{
+       DB *db;
+       datum data, key;
+
+       key.dptr = "foo";
+       key.dsize = strlen("foo");
+       data.dptr = "baz";
+       data.dsize = strlen("baz");
+       create_db();
+
+       db = dbm_open(path, O_WRONLY, 0755);
+       data = dbm_fetch(db, key);
+       ATF_REQUIRE(data.dptr == NULL);
+       ATF_REQUIRE(errno == EPERM);
+       ATF_REQUIRE(dbm_store(db, key, data, DBM_REPLACE) != -1);
+}
+
+ATF_TC_WITHOUT_HEAD(dbm_rdwr_test);
+ATF_TC_BODY(dbm_rdwr_test, tc)
+{
+       DB *db;
+       datum data, key;
+
+       key.dptr = "foo";
+       key.dsize = strlen("foo");
+       create_db();
+
+       db = dbm_open(path, O_RDWR, 0755);
+       data = dbm_fetch(db, key);
+       ATF_REQUIRE(data.dptr != NULL);
+       data.dptr = "baz";
+       data.dsize = strlen("baz");
+       ATF_REQUIRE(dbm_store(db, key, data, DBM_REPLACE) != -1);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+       ATF_TP_ADD_TC(tp, dbm_rdonly_test);
+       ATF_TP_ADD_TC(tp, dbm_wronly_test);
+       ATF_TP_ADD_TC(tp, dbm_rdwr_test);
+
+       return (atf_no_error());
+}

Reply via email to