507 lines
16 KiB
Diff
507 lines
16 KiB
Diff
From a5c570e262911874e43e82de601d809aa5b1b729 Mon Sep 17 00:00:00 2001
|
||
From: Ralph Boehme <slow@samba.org>
|
||
Date: Sat, 17 Jun 2023 13:53:27 +0200
|
||
Subject: [PATCH 16/28] CVE-2023-34968: mdscli: return share relative paths
|
||
MIME-Version: 1.0
|
||
Content-Type: text/plain; charset=UTF-8
|
||
Content-Transfer-Encoding: 8bit
|
||
|
||
The next commit will change the Samba Spotlight server to return absolute paths
|
||
that start with the sharename as "/SHARENAME/..." followed by the share path
|
||
relative appended.
|
||
|
||
So given a share
|
||
|
||
[spotlight]
|
||
path = /foo/bar
|
||
spotlight = yes
|
||
|
||
and a file inside this share with a full path of
|
||
|
||
/foo/bar/dir/file
|
||
|
||
previously a search that matched this file would returns the absolute
|
||
server-side pato of the file, ie
|
||
|
||
/foo/bar/dir/file
|
||
|
||
This will be change to
|
||
|
||
/spotlight/dir/file
|
||
|
||
As currently the mdscli library and hence the mdsearch tool print out these
|
||
paths returned from the server, we have to change the output to accomodate these
|
||
fake paths. The only way to do this sensibly is by makeing the paths relative to
|
||
the containing share, so just
|
||
|
||
dir/file
|
||
|
||
in the example above.
|
||
|
||
The client learns about the share root path prefix 鈥<> real server-side of fake in
|
||
the future 鈥<> in an initial handshake in the "share_path" out argument of the
|
||
mdssvc_open() RPC call, so the client can use this path to convert the absolute
|
||
path to relative.
|
||
|
||
There is however an additional twist: the macOS Spotlight server prefixes this
|
||
absolute path with another prefix, typically "/System/Volumes/Data", so in the
|
||
example above the full path for the same search would be
|
||
|
||
/System/Volumes/Data/foo/bar/dir/file
|
||
|
||
So macOS does return the full server-side path too, just prefixed with an
|
||
additional path. This path prefixed can be queried by the client in the
|
||
mdssvc_cmd() RPC call with an Spotlight command of "fetchPropertiesForContext:"
|
||
and the path is returned in a dictionary with key "kMDSStorePathScopes". Samba
|
||
just returns "/" for this.
|
||
|
||
Currently the mdscli library doesn't issue this Spotlight RPC
|
||
request (fetchPropertiesForContext), so this is added in this commit. In the
|
||
end, all search result paths are stripped of the combined prefix
|
||
|
||
kMDSStorePathScopes + share_path (from mdssvc_open).
|
||
|
||
eg
|
||
|
||
kMDSStorePathScopes = /System/Volumes/Data
|
||
share_path = /foo/bar
|
||
search result = /System/Volumes/Data/foo/bar/dir/file
|
||
relative path returned by mdscli = dir/file
|
||
|
||
Makes sense? :)
|
||
|
||
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15388
|
||
|
||
Signed-off-by: Ralph Boehme <slow@samba.org>
|
||
Reviewed-by: Stefan Metzmacher <metze@samba.org>
|
||
|
||
Conflict: NA
|
||
Reference: https://download.samba.org/pub/samba/patches/security/samba-4.17.10-security-2023-07-19.patch
|
||
---
|
||
python/samba/tests/blackbox/mdsearch.py | 8 +-
|
||
python/samba/tests/dcerpc/mdssvc.py | 26 ++--
|
||
source3/rpc_client/cli_mdssvc.c | 155 +++++++++++++++++++++++-
|
||
source3/rpc_client/cli_mdssvc_private.h | 4 +
|
||
source3/rpc_client/cli_mdssvc_util.c | 68 +++++++++++
|
||
source3/rpc_client/cli_mdssvc_util.h | 4 +
|
||
6 files changed, 245 insertions(+), 20 deletions(-)
|
||
|
||
diff --git a/python/samba/tests/blackbox/mdsearch.py b/python/samba/tests/blackbox/mdsearch.py
|
||
index c9156ae6e0e..c8e75661f15 100644
|
||
--- a/python/samba/tests/blackbox/mdsearch.py
|
||
+++ b/python/samba/tests/blackbox/mdsearch.py
|
||
@@ -76,10 +76,7 @@ class MdfindBlackboxTests(BlackboxTestCase):
|
||
self.t.start()
|
||
time.sleep(1)
|
||
|
||
- pipe = mdssvc.mdssvc('ncacn_np:fileserver[/pipe/mdssvc]', self.get_loadparm())
|
||
- conn = mdscli.conn(pipe, 'spotlight', '/foo')
|
||
- self.sharepath = conn.sharepath()
|
||
- conn.disconnect(pipe)
|
||
+ self.sharepath = os.environ["LOCAL_PATH"]
|
||
|
||
for file in testfiles:
|
||
f = open("%s/%s" % (self.sharepath, file), "w")
|
||
@@ -126,5 +123,4 @@ class MdfindBlackboxTests(BlackboxTestCase):
|
||
output = self.check_output("mdsearch --configfile=%s -U %s%%%s fileserver spotlight '*==\"samba*\"'" % (config, username, password))
|
||
|
||
actual = output.decode('utf-8').splitlines()
|
||
- expected = ["%s/%s" % (self.sharepath, file) for file in testfiles]
|
||
- self.assertEqual(expected, actual)
|
||
+ self.assertEqual(testfiles, actual)
|
||
diff --git a/python/samba/tests/dcerpc/mdssvc.py b/python/samba/tests/dcerpc/mdssvc.py
|
||
index b0df509ddc7..5002e5d26d6 100644
|
||
--- a/python/samba/tests/dcerpc/mdssvc.py
|
||
+++ b/python/samba/tests/dcerpc/mdssvc.py
|
||
@@ -84,10 +84,11 @@ class MdssvcTests(RpcInterfaceTestCase):
|
||
self.t = threading.Thread(target=MdssvcTests.http_server, args=(self,))
|
||
self.t.setDaemon(True)
|
||
self.t.start()
|
||
+ self.sharepath = os.environ["LOCAL_PATH"]
|
||
time.sleep(1)
|
||
|
||
conn = mdscli.conn(self.pipe, 'spotlight', '/foo')
|
||
- self.sharepath = conn.sharepath()
|
||
+ self.fakepath = conn.sharepath()
|
||
conn.disconnect(self.pipe)
|
||
|
||
for file in testfiles:
|
||
@@ -105,12 +106,11 @@ class MdssvcTests(RpcInterfaceTestCase):
|
||
self.server.serve_forever()
|
||
|
||
def run_test(self, query, expect, json_in, json_out):
|
||
- expect = [s.replace("%BASEPATH%", self.sharepath) for s in expect]
|
||
self.server.json_in = json_in.replace("%BASEPATH%", self.sharepath)
|
||
self.server.json_out = json_out.replace("%BASEPATH%", self.sharepath)
|
||
|
||
self.conn = mdscli.conn(self.pipe, 'spotlight', '/foo')
|
||
- search = self.conn.search(self.pipe, query, self.sharepath)
|
||
+ search = self.conn.search(self.pipe, query, self.fakepath)
|
||
|
||
# Give it some time, the get_results() below returns immediately
|
||
# what's available, so if we ask to soon, we might get back no results
|
||
@@ -141,7 +141,7 @@ class MdssvcTests(RpcInterfaceTestCase):
|
||
]
|
||
}
|
||
}'''
|
||
- exp_results = ["%BASEPATH%/foo", "%BASEPATH%/bar"]
|
||
+ exp_results = ["foo", "bar"]
|
||
self.run_test('*=="samba*"', exp_results, exp_json_query, fake_json_response)
|
||
|
||
def test_mdscli_search_escapes(self):
|
||
@@ -181,14 +181,14 @@ class MdssvcTests(RpcInterfaceTestCase):
|
||
}
|
||
}'''
|
||
exp_results = [
|
||
- r"%BASEPATH%/x+x",
|
||
- r"%BASEPATH%/x*x",
|
||
- r"%BASEPATH%/x=x",
|
||
- r"%BASEPATH%/x'x",
|
||
- r"%BASEPATH%/x?x",
|
||
- r"%BASEPATH%/x x",
|
||
- r"%BASEPATH%/x(x",
|
||
- "%BASEPATH%/x\"x",
|
||
- r"%BASEPATH%/x\x",
|
||
+ r"x+x",
|
||
+ r"x*x",
|
||
+ r"x=x",
|
||
+ r"x'x",
|
||
+ r"x?x",
|
||
+ r"x x",
|
||
+ r"x(x",
|
||
+ "x\"x",
|
||
+ r"x\x",
|
||
]
|
||
self.run_test(sl_query, exp_results, exp_json_query, fake_json_response)
|
||
diff --git a/source3/rpc_client/cli_mdssvc.c b/source3/rpc_client/cli_mdssvc.c
|
||
index 474d7c0b150..753bc2e52ed 100644
|
||
--- a/source3/rpc_client/cli_mdssvc.c
|
||
+++ b/source3/rpc_client/cli_mdssvc.c
|
||
@@ -43,10 +43,12 @@ char *mdscli_get_basepath(TALLOC_CTX *mem_ctx,
|
||
struct mdscli_connect_state {
|
||
struct tevent_context *ev;
|
||
struct mdscli_ctx *mdscli_ctx;
|
||
+ struct mdssvc_blob response_blob;
|
||
};
|
||
|
||
static void mdscli_connect_open_done(struct tevent_req *subreq);
|
||
static void mdscli_connect_unknown1_done(struct tevent_req *subreq);
|
||
+static void mdscli_connect_fetch_props_done(struct tevent_req *subreq);
|
||
|
||
struct tevent_req *mdscli_connect_send(TALLOC_CTX *mem_ctx,
|
||
struct tevent_context *ev,
|
||
@@ -111,6 +113,7 @@ static void mdscli_connect_open_done(struct tevent_req *subreq)
|
||
struct mdscli_connect_state *state = tevent_req_data(
|
||
req, struct mdscli_connect_state);
|
||
struct mdscli_ctx *mdscli_ctx = state->mdscli_ctx;
|
||
+ size_t share_path_len;
|
||
NTSTATUS status;
|
||
|
||
status = dcerpc_mdssvc_open_recv(subreq, state);
|
||
@@ -120,6 +123,18 @@ static void mdscli_connect_open_done(struct tevent_req *subreq)
|
||
return;
|
||
}
|
||
|
||
+ share_path_len = strlen(mdscli_ctx->mdscmd_open.share_path);
|
||
+ if (share_path_len < 1 || share_path_len > UINT16_MAX) {
|
||
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
|
||
+ return;
|
||
+ }
|
||
+ mdscli_ctx->mdscmd_open.share_path_len = share_path_len;
|
||
+
|
||
+ if (mdscli_ctx->mdscmd_open.share_path[share_path_len-1] == '/') {
|
||
+ mdscli_ctx->mdscmd_open.share_path[share_path_len-1] = '\0';
|
||
+ mdscli_ctx->mdscmd_open.share_path_len--;
|
||
+ }
|
||
+
|
||
subreq = dcerpc_mdssvc_unknown1_send(
|
||
state,
|
||
state->ev,
|
||
@@ -146,6 +161,8 @@ static void mdscli_connect_unknown1_done(struct tevent_req *subreq)
|
||
subreq, struct tevent_req);
|
||
struct mdscli_connect_state *state = tevent_req_data(
|
||
req, struct mdscli_connect_state);
|
||
+ struct mdscli_ctx *mdscli_ctx = state->mdscli_ctx;
|
||
+ struct mdssvc_blob request_blob;
|
||
NTSTATUS status;
|
||
|
||
status = dcerpc_mdssvc_unknown1_recv(subreq, state);
|
||
@@ -154,6 +171,108 @@ static void mdscli_connect_unknown1_done(struct tevent_req *subreq)
|
||
return;
|
||
}
|
||
|
||
+ status = mdscli_blob_fetch_props(state,
|
||
+ state->mdscli_ctx,
|
||
+ &request_blob);
|
||
+ if (tevent_req_nterror(req, status)) {
|
||
+ return;
|
||
+ }
|
||
+
|
||
+ subreq = dcerpc_mdssvc_cmd_send(state,
|
||
+ state->ev,
|
||
+ mdscli_ctx->bh,
|
||
+ &mdscli_ctx->ph,
|
||
+ 0,
|
||
+ mdscli_ctx->dev,
|
||
+ mdscli_ctx->mdscmd_open.unkn2,
|
||
+ 0,
|
||
+ mdscli_ctx->flags,
|
||
+ request_blob,
|
||
+ 0,
|
||
+ mdscli_ctx->max_fragment_size,
|
||
+ 1,
|
||
+ mdscli_ctx->max_fragment_size,
|
||
+ 0,
|
||
+ 0,
|
||
+ &mdscli_ctx->mdscmd_cmd.fragment,
|
||
+ &state->response_blob,
|
||
+ &mdscli_ctx->mdscmd_cmd.unkn9);
|
||
+ if (tevent_req_nomem(subreq, req)) {
|
||
+ return;
|
||
+ }
|
||
+ tevent_req_set_callback(subreq, mdscli_connect_fetch_props_done, req);
|
||
+ mdscli_ctx->async_pending++;
|
||
+ return;
|
||
+}
|
||
+
|
||
+static void mdscli_connect_fetch_props_done(struct tevent_req *subreq)
|
||
+{
|
||
+ struct tevent_req *req = tevent_req_callback_data(
|
||
+ subreq, struct tevent_req);
|
||
+ struct mdscli_connect_state *state = tevent_req_data(
|
||
+ req, struct mdscli_connect_state);
|
||
+ struct mdscli_ctx *mdscli_ctx = state->mdscli_ctx;
|
||
+ DALLOC_CTX *d = NULL;
|
||
+ sl_array_t *path_scope_array = NULL;
|
||
+ char *path_scope = NULL;
|
||
+ NTSTATUS status;
|
||
+ bool ok;
|
||
+
|
||
+ status = dcerpc_mdssvc_cmd_recv(subreq, state);
|
||
+ TALLOC_FREE(subreq);
|
||
+ state->mdscli_ctx->async_pending--;
|
||
+ if (tevent_req_nterror(req, status)) {
|
||
+ return;
|
||
+ }
|
||
+
|
||
+ d = dalloc_new(state);
|
||
+ if (tevent_req_nomem(d, req)) {
|
||
+ return;
|
||
+ }
|
||
+
|
||
+ ok = sl_unpack(d,
|
||
+ (char *)state->response_blob.spotlight_blob,
|
||
+ state->response_blob.length);
|
||
+ if (!ok) {
|
||
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
|
||
+ return;
|
||
+ }
|
||
+
|
||
+ path_scope_array = dalloc_value_for_key(d,
|
||
+ "DALLOC_CTX", 0,
|
||
+ "kMDSStorePathScopes",
|
||
+ "sl_array_t");
|
||
+ if (path_scope_array == NULL) {
|
||
+ DBG_ERR("Missing kMDSStorePathScopes\n");
|
||
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
|
||
+ return;
|
||
+ }
|
||
+
|
||
+ path_scope = dalloc_get(path_scope_array, "char *", 0);
|
||
+ if (path_scope == NULL) {
|
||
+ DBG_ERR("Missing path in kMDSStorePathScopes\n");
|
||
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
|
||
+ return;
|
||
+ }
|
||
+
|
||
+ mdscli_ctx->path_scope_len = strlen(path_scope);
|
||
+ if (mdscli_ctx->path_scope_len < 1 ||
|
||
+ mdscli_ctx->path_scope_len > UINT16_MAX)
|
||
+ {
|
||
+ DBG_ERR("Bad path_scope: %s\n", path_scope);
|
||
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
|
||
+ return;
|
||
+ }
|
||
+ mdscli_ctx->path_scope = talloc_strdup(mdscli_ctx, path_scope);
|
||
+ if (tevent_req_nomem(mdscli_ctx->path_scope, req)) {
|
||
+ return;
|
||
+ }
|
||
+
|
||
+ if (mdscli_ctx->path_scope[mdscli_ctx->path_scope_len-1] == '/') {
|
||
+ mdscli_ctx->path_scope[mdscli_ctx->path_scope_len-1] = '\0';
|
||
+ mdscli_ctx->path_scope_len--;
|
||
+ }
|
||
+
|
||
tevent_req_done(req);
|
||
}
|
||
|
||
@@ -697,7 +816,10 @@ static void mdscli_get_path_done(struct tevent_req *subreq)
|
||
struct mdscli_get_path_state *state = tevent_req_data(
|
||
req, struct mdscli_get_path_state);
|
||
DALLOC_CTX *d = NULL;
|
||
+ size_t pathlen;
|
||
+ size_t prefixlen;
|
||
char *path = NULL;
|
||
+ const char *p = NULL;
|
||
NTSTATUS status;
|
||
bool ok;
|
||
|
||
@@ -732,7 +854,38 @@ static void mdscli_get_path_done(struct tevent_req *subreq)
|
||
tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
|
||
return;
|
||
}
|
||
- state->path = talloc_move(state, &path);
|
||
+
|
||
+ /* Path is prefixed by /PATHSCOPE/SHARENAME/, strip it */
|
||
+ pathlen = strlen(path);
|
||
+
|
||
+ /*
|
||
+ * path_scope_len and share_path_len are already checked to be smaller
|
||
+ * then UINT16_MAX so this can't overflow
|
||
+ */
|
||
+ prefixlen = state->mdscli_ctx->path_scope_len
|
||
+ + state->mdscli_ctx->mdscmd_open.share_path_len;
|
||
+
|
||
+ if (pathlen < prefixlen) {
|
||
+ DBG_DEBUG("Bad path: %s\n", path);
|
||
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
|
||
+ return;
|
||
+ }
|
||
+
|
||
+ p = path + prefixlen;
|
||
+ while (*p == '/') {
|
||
+ p++;
|
||
+ }
|
||
+ if (*p == '\0') {
|
||
+ DBG_DEBUG("Bad path: %s\n", path);
|
||
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
|
||
+ return;
|
||
+ }
|
||
+
|
||
+ state->path = talloc_strdup(state, p);
|
||
+ if (state->path == NULL) {
|
||
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
|
||
+ return;
|
||
+ }
|
||
DBG_DEBUG("path: %s\n", state->path);
|
||
|
||
tevent_req_done(req);
|
||
diff --git a/source3/rpc_client/cli_mdssvc_private.h b/source3/rpc_client/cli_mdssvc_private.h
|
||
index 031af85bf58..77f300c09cc 100644
|
||
--- a/source3/rpc_client/cli_mdssvc_private.h
|
||
+++ b/source3/rpc_client/cli_mdssvc_private.h
|
||
@@ -42,6 +42,7 @@ struct mdscli_ctx {
|
||
/* cmd specific or unknown fields */
|
||
struct {
|
||
char share_path[1025];
|
||
+ size_t share_path_len;
|
||
uint32_t unkn2;
|
||
uint32_t unkn3;
|
||
} mdscmd_open;
|
||
@@ -56,6 +57,9 @@ struct mdscli_ctx {
|
||
struct {
|
||
uint32_t status;
|
||
} mdscmd_close;
|
||
+
|
||
+ char *path_scope;
|
||
+ size_t path_scope_len;
|
||
};
|
||
|
||
struct mdscli_search_ctx {
|
||
diff --git a/source3/rpc_client/cli_mdssvc_util.c b/source3/rpc_client/cli_mdssvc_util.c
|
||
index a39202d0c99..1eaaca715a8 100644
|
||
--- a/source3/rpc_client/cli_mdssvc_util.c
|
||
+++ b/source3/rpc_client/cli_mdssvc_util.c
|
||
@@ -28,6 +28,74 @@
|
||
#include "rpc_server/mdssvc/dalloc.h"
|
||
#include "rpc_server/mdssvc/marshalling.h"
|
||
|
||
+NTSTATUS mdscli_blob_fetch_props(TALLOC_CTX *mem_ctx,
|
||
+ struct mdscli_ctx *ctx,
|
||
+ struct mdssvc_blob *blob)
|
||
+{
|
||
+ DALLOC_CTX *d = NULL;
|
||
+ uint64_t *uint64p = NULL;
|
||
+ sl_array_t *array = NULL;
|
||
+ sl_array_t *cmd_array = NULL;
|
||
+ NTSTATUS status;
|
||
+ int ret;
|
||
+
|
||
+ d = dalloc_new(mem_ctx);
|
||
+ if (d == NULL) {
|
||
+ return NT_STATUS_NO_MEMORY;
|
||
+ }
|
||
+
|
||
+ array = dalloc_zero(d, sl_array_t);
|
||
+ if (array == NULL) {
|
||
+ TALLOC_FREE(d);
|
||
+ return NT_STATUS_NO_MEMORY;
|
||
+ }
|
||
+
|
||
+ ret = dalloc_add(d, array, sl_array_t);
|
||
+ if (ret != 0) {
|
||
+ TALLOC_FREE(d);
|
||
+ return NT_STATUS_NO_MEMORY;
|
||
+ }
|
||
+
|
||
+ cmd_array = dalloc_zero(d, sl_array_t);
|
||
+ if (cmd_array == NULL) {
|
||
+ TALLOC_FREE(d);
|
||
+ return NT_STATUS_NO_MEMORY;
|
||
+ }
|
||
+
|
||
+ ret = dalloc_add(array, cmd_array, sl_array_t);
|
||
+ if (ret != 0) {
|
||
+ TALLOC_FREE(d);
|
||
+ return NT_STATUS_NO_MEMORY;
|
||
+ }
|
||
+
|
||
+ ret = dalloc_stradd(cmd_array, "fetchPropertiesForContext:");
|
||
+ if (ret != 0) {
|
||
+ TALLOC_FREE(d);
|
||
+ return NT_STATUS_NO_MEMORY;
|
||
+ }
|
||
+
|
||
+ uint64p = talloc_zero_array(cmd_array, uint64_t, 2);
|
||
+ if (uint64p == NULL) {
|
||
+ TALLOC_FREE(d);
|
||
+ return NT_STATUS_NO_MEMORY;
|
||
+ }
|
||
+
|
||
+ talloc_set_name(uint64p, "uint64_t *");
|
||
+
|
||
+ ret = dalloc_add(cmd_array, uint64p, uint64_t *);
|
||
+ if (ret != 0) {
|
||
+ TALLOC_FREE(d);
|
||
+ return NT_STATUS_NO_MEMORY;
|
||
+ }
|
||
+
|
||
+ status = sl_pack_alloc(mem_ctx, d, blob, ctx->max_fragment_size);
|
||
+ TALLOC_FREE(d);
|
||
+ if (!NT_STATUS_IS_OK(status)) {
|
||
+ return status;
|
||
+ }
|
||
+ return NT_STATUS_OK;
|
||
+}
|
||
+
|
||
NTSTATUS mdscli_blob_search(TALLOC_CTX *mem_ctx,
|
||
struct mdscli_search_ctx *search,
|
||
struct mdssvc_blob *blob)
|
||
diff --git a/source3/rpc_client/cli_mdssvc_util.h b/source3/rpc_client/cli_mdssvc_util.h
|
||
index 7a98c854526..3f324758c70 100644
|
||
--- a/source3/rpc_client/cli_mdssvc_util.h
|
||
+++ b/source3/rpc_client/cli_mdssvc_util.h
|
||
@@ -21,6 +21,10 @@
|
||
#ifndef _MDSCLI_UTIL_H_
|
||
#define _MDSCLI_UTIL_H_
|
||
|
||
+NTSTATUS mdscli_blob_fetch_props(TALLOC_CTX *mem_ctx,
|
||
+ struct mdscli_ctx *ctx,
|
||
+ struct mdssvc_blob *blob);
|
||
+
|
||
NTSTATUS mdscli_blob_search(TALLOC_CTX *mem_ctx,
|
||
struct mdscli_search_ctx *search,
|
||
struct mdssvc_blob *blob);
|
||
--
|
||
2.34.1
|