The branch, master has been updated
       via  1e899521e82 CVE-2025-9640: s3/modules/vfs_streams_xattr fix 
unitialized write
       via  59158cc3b74 CVE-2025-9640: Add torture test for inserting hole in 
stream
      from  e28dc4e98f1 smbtorture: fix regression in smb2.bench

https://git.samba.org/?p=samba.git;a=shortlog;h=master


- Log -----------------------------------------------------------------
commit 1e899521e821f2ee4cbb93f6a3befd37f5ba0403
Author: Andrew Walker <[email protected]>
Date:   Thu Aug 28 19:36:19 2025 +0000

    CVE-2025-9640: s3/modules/vfs_streams_xattr fix unitialized write
    
    This commit fixes a situation in which vfs_streams_xattr could
    write unitialized memory into alternate data streams if the
    user writes to an offset that is beyond the current end of file
    to insert a hole in it.
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=15885
    
    Signed-off-by: Andrew Walker <[email protected]>
    Reviewed-by: Volker Lendecke <[email protected]>
    
    Autobuild-User(master): Volker Lendecke <[email protected]>
    Autobuild-Date(master): Thu Oct 16 19:47:19 UTC 2025 on atb-devel-224

commit 59158cc3b74835193e0b058561206a3198adc3fe
Author: Andrew Walker <[email protected]>
Date:   Thu Aug 28 19:39:34 2025 +0000

    CVE-2025-9640: Add torture test for inserting hole in stream
    
    This commit adds an smb torture test for inserting a hole into
    an alternate data stream and then verifying that hole contains
    null bytes.
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=15885
    
    Signed-off-by: Andrew Walker <[email protected]>
    Reviewed-by: Volker Lendecke <[email protected]>

-----------------------------------------------------------------------

Summary of changes:
 source3/modules/vfs_streams_xattr.c |   5 +-
 source3/selftest/tests.py           |   3 +
 source4/torture/vfs/streams_xattr.c | 211 ++++++++++++++++++++++++++++++++++++
 source4/torture/vfs/vfs.c           |   1 +
 source4/torture/wscript_build       |   2 +-
 5 files changed, 220 insertions(+), 2 deletions(-)
 create mode 100644 source4/torture/vfs/streams_xattr.c


Changeset truncated at 500 lines:

diff --git a/source3/modules/vfs_streams_xattr.c 
b/source3/modules/vfs_streams_xattr.c
index 93044924b34..7ac67d3fb98 100644
--- a/source3/modules/vfs_streams_xattr.c
+++ b/source3/modules/vfs_streams_xattr.c
@@ -1051,15 +1051,18 @@ static ssize_t streams_xattr_pwrite(vfs_handle_struct 
*handle,
 
         if ((offset + n) > ea.value.length-1) {
                uint8_t *tmp;
+               size_t new_sz = offset + n + 1;
 
                tmp = talloc_realloc(talloc_tos(), ea.value.data, uint8_t,
-                                          offset + n + 1);
+                                          new_sz);
 
                if (tmp == NULL) {
                        TALLOC_FREE(ea.value.data);
                         errno = ENOMEM;
                         return -1;
                 }
+
+               memset(tmp + ea.value.length, 0, new_sz - ea.value.length);
                ea.value.data = tmp;
                ea.value.length = offset + n + 1;
                ea.value.data[offset+n] = 0;
diff --git a/source3/selftest/tests.py b/source3/selftest/tests.py
index dad58fca5f2..efba899a920 100755
--- a/source3/selftest/tests.py
+++ b/source3/selftest/tests.py
@@ -1163,6 +1163,7 @@ nbt = ["nbt.dgram"]
 vfs = [
     "vfs.fruit",
     "vfs.acl_xattr",
+    "vfs.streams_xattr",
     "vfs.fruit_netatalk",
     "vfs.fruit_file_id",
     "vfs.fruit_timemachine",
@@ -1359,6 +1360,8 @@ for t in tests:
             plansmbtorture4testsuite(t, "fileserver", '//$SERVER_IP/tmp 
-U$USERNAME%$PASSWORD')
     elif t == "vfs.acl_xattr":
         plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/tmp 
-U$USERNAME%$PASSWORD')
+    elif t == "vfs.streams_xattr":
+        plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/vfs_wo_fruit 
-U$USERNAME%$PASSWORD')
     elif t == "smb2.compound_find":
         plansmbtorture4testsuite(t, "fileserver", '//$SERVER/compound_find 
-U$USERNAME%$PASSWORD')
         plansmbtorture4testsuite(t, "fileserver", '//$SERVER_IP/tmp 
-U$USERNAME%$PASSWORD')
diff --git a/source4/torture/vfs/streams_xattr.c 
b/source4/torture/vfs/streams_xattr.c
new file mode 100644
index 00000000000..0eb83e092e7
--- /dev/null
+++ b/source4/torture/vfs/streams_xattr.c
@@ -0,0 +1,211 @@
+/*
+   Unix SMB/CIFS implementation.
+
+   Copyright (C) Andrew Walker (2025)
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "lib/cmdline/cmdline.h"
+#include "libcli/smb2/smb2.h"
+#include "libcli/smb2/smb2_calls.h"
+#include "libcli/smb/smbXcli_base.h"
+#include "torture/torture.h"
+#include "torture/vfs/proto.h"
+#include "libcli/resolve/resolve.h"
+#include "torture/util.h"
+#include "torture/smb2/proto.h"
+#include "lib/param/param.h"
+
+#define BASEDIR "smb2-testads"
+
+
+static bool get_stream_handle(struct torture_context *tctx,
+                             struct smb2_tree *tree,
+                             const char *dname,
+                             const char *fname,
+                             const char *sname,
+                             struct smb2_handle *hdl_in)
+{
+       bool ret = true;
+       NTSTATUS status;
+       struct smb2_handle fhandle = {{0}};
+       struct smb2_handle dhandle = {{0}};
+
+       torture_comment(tctx, "Create dir\n");
+
+       status = torture_smb2_testdir(tree, dname, &dhandle);
+       torture_assert_ntstatus_ok_goto(tctx, status, ret, done, 
"torture_smb2_testdir\n");
+
+       torture_comment(tctx, "Create file\n");
+
+       status = torture_smb2_testfile(tree, fname, &fhandle);
+       torture_assert_ntstatus_ok_goto(tctx, status, ret, done, 
"torture_smb2_testfile\n");
+
+       status = torture_smb2_testfile(tree, sname, hdl_in);
+       torture_assert_ntstatus_ok_goto(tctx, status, ret, done, 
"torture_smb2_testfile\n");
+
+done:
+       if (!smb2_util_handle_empty(fhandle)) {
+               smb2_util_close(tree, fhandle);
+       }
+       if (!smb2_util_handle_empty(dhandle)) {
+               smb2_util_close(tree, dhandle);
+       }
+       return ret;
+}
+
+static bool read_stream(struct torture_context *tctx,
+                       TALLOC_CTX *mem_ctx,
+                       struct smb2_tree *tree,
+                       struct smb2_handle *stream_hdl,
+                       off_t read_offset,
+                       size_t read_count,
+                       char **data_out,
+                       size_t *data_out_sz)
+{
+       NTSTATUS status;
+       struct smb2_read r;
+       bool ret = true;
+
+       ZERO_STRUCT(r);
+       r.in.file.handle = *stream_hdl;
+       r.in.length = read_count;
+       r.in.offset = read_offset;
+
+       status = smb2_read(tree, mem_ctx, &r);
+       torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "stream 
read\n");
+
+       *data_out = (char *)r.out.data.data;
+       *data_out_sz = r.out.data.length;
+
+done:
+       return ret;
+}
+
+
+#define WRITE_PAYLOAD "canary"
+#define ADS_LEN 1024
+#define ADS_OFF_TAIL ADS_LEN - sizeof(WRITE_PAYLOAD)
+
+static bool test_streams_pwrite_hole(struct torture_context *tctx,
+                                    struct smb2_tree *tree)
+{
+       NTSTATUS status;
+       bool ok;
+       bool ret = true;
+       const char *dname = BASEDIR "\\testdir";
+       const char *fname = BASEDIR "\\testdir\\testfile";
+       const char *sname = BASEDIR "\\testdir\\testfile:test_stream";
+       const char *canary = "canary";
+       struct smb2_handle shandle = {{0}};
+       TALLOC_CTX *tmp_ctx = NULL;
+       char *data = NULL;
+       size_t data_sz, i;
+
+       ok = smb2_util_setup_dir(tctx, tree, BASEDIR);
+       torture_assert_goto(tctx, ok == true, ret, done, "Unable to setup 
testdir\n");
+
+       tmp_ctx = talloc_new(tree);
+       torture_assert_goto(tctx, tmp_ctx != NULL, ret, done, "Memory 
failure\n");
+
+       ok = get_stream_handle(tctx, tree, dname, fname, sname, &shandle);
+       if (!ok) {
+               // torture assert already set
+               goto done;
+       }
+
+       /*
+        * We're going to write a string at the beginning at the ADS, then 
write the same
+        * string at a later offset, introducing a hole in the file
+        */
+       torture_comment(tctx, "writing at varying offsets to create hole\n");
+       status = smb2_util_write(tree, shandle, WRITE_PAYLOAD, 0, 
sizeof(WRITE_PAYLOAD));
+       if (!NT_STATUS_IS_OK(status)) {
+               torture_comment(tctx, "Failed to write %zu bytes to "
+                   "stream at offset 0\n", sizeof(canary));
+               return false;
+       }
+
+       status = smb2_util_write(tree, shandle, WRITE_PAYLOAD, ADS_OFF_TAIL, 
sizeof(WRITE_PAYLOAD));
+       if (!NT_STATUS_IS_OK(status)) {
+               torture_comment(tctx, "Failed to write %zu bytes to "
+                   "stream at offset 1018\n", sizeof(canary));
+               return false;
+       }
+
+       /* Now we'll read the stream contents */
+       torture_comment(tctx, "Read stream data\n");
+       ok = read_stream(tctx, tmp_ctx, tree, &shandle, 0, ADS_LEN, &data, 
&data_sz);
+       if (!ok) {
+               // torture assert already set
+               goto done;
+       }
+
+       torture_assert_goto(tctx, data_sz == ADS_LEN, ret, done, "Short read on 
ADS\n");
+
+       /* Make sure our strings actually got written */
+       if (strncmp(data, WRITE_PAYLOAD, sizeof(WRITE_PAYLOAD)) != 0) {
+               torture_result(tctx, TORTURE_FAIL,
+                              "Payload write at beginning of file failed");
+               ret = false;
+               goto done;
+       }
+
+       if (strncmp(data + ADS_OFF_TAIL, WRITE_PAYLOAD, sizeof(WRITE_PAYLOAD)) 
!= 0) {
+               torture_result(tctx, TORTURE_FAIL,
+                              "Payload write at end of file failed");
+               ret = false;
+               goto done;
+       }
+
+       /* Now we'll check that the hole is full of null bytes */
+       for (i = sizeof(WRITE_PAYLOAD); i < ADS_OFF_TAIL; i++) {
+               if (data[i] != '\0') {
+                       torture_comment(tctx, "idx: %zu, got 0x%02x when 
expected 0x00\n",
+                                       i, (uint8_t)data[i]);
+                       torture_result(tctx, TORTURE_FAIL,
+                                      "0x%08x: unexpected non-null byte in ADS 
read\n",
+                                      data[i]);
+                       ret = false;
+                       goto done;
+               }
+       }
+
+done:
+       talloc_free(tmp_ctx);
+
+       if (!smb2_util_handle_empty(shandle)) {
+               smb2_util_close(tree, shandle);
+       }
+
+       smb2_deltree(tree, BASEDIR);
+
+       return ret;
+}
+
+/*
+   basic testing of vfs_streams_xattr
+*/
+struct torture_suite *torture_vfs_streams_xattr(TALLOC_CTX *ctx)
+{
+       struct torture_suite *suite = torture_suite_create(ctx, 
"streams_xattr");
+
+       torture_suite_add_1smb2_test(suite, "streams-pwrite-hole", 
test_streams_pwrite_hole);
+
+       suite->description = talloc_strdup(suite, "vfs_streams_xattr tests");
+
+       return suite;
+}
diff --git a/source4/torture/vfs/vfs.c b/source4/torture/vfs/vfs.c
index 3d402eeee0d..19dbaa0775c 100644
--- a/source4/torture/vfs/vfs.c
+++ b/source4/torture/vfs/vfs.c
@@ -115,6 +115,7 @@ NTSTATUS torture_vfs_init(TALLOC_CTX *ctx)
        torture_suite_add_suite(suite, torture_vfs_fruit_timemachine(suite));
        torture_suite_add_suite(suite, torture_vfs_fruit_conversion(suite));
        torture_suite_add_suite(suite, torture_vfs_fruit_unfruit(suite));
+       torture_suite_add_suite(suite, torture_vfs_streams_xattr(suite));
        torture_suite_add_1smb2_test(suite, "fruit_validate_afpinfo", 
test_fruit_validate_afpinfo);
 
        torture_register_suite(ctx, suite);
diff --git a/source4/torture/wscript_build b/source4/torture/wscript_build
index b38a30c98da..cae558398a3 100644
--- a/source4/torture/wscript_build
+++ b/source4/torture/wscript_build
@@ -301,7 +301,7 @@ bld.SAMBA_MODULE('TORTURE_NTP',
        )
 
 bld.SAMBA_MODULE('TORTURE_VFS',
-       source='vfs/vfs.c vfs/fruit.c vfs/acl_xattr.c',
+       source='vfs/vfs.c vfs/fruit.c vfs/acl_xattr.c vfs/streams_xattr.c',
        subsystem='smbtorture',
        deps='LIBCLI_SMB TORTURE_UTIL smbclient-raw TORTURE_RAW',
        internal_module=True,


-- 
Samba Shared Repository

Reply via email to