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