Skip to content

Commit ee91945

Browse files
committed
MB-60066: Tolerate DCP deletions with value larger than 1MiB
Subdoc CreateWithDelete can legitimately create a user xattr that exceeds 1MiB, yet the DCP validation expects deletes to only come with <= 1MiB of value. Change the incorrect validation, now matches dcp_mutation.cc checks Change-Id: I9863495db104ca10f8d651f61ee68a844055a027 Reviewed-on: https://review.couchbase.org/c/kv_engine/+/202507 Well-Formed: Restriction Checker Reviewed-by: Trond Norbye <[email protected]> Tested-by: Jim Walker <[email protected]>
1 parent 62333fe commit ee91945

File tree

3 files changed

+86
-37
lines changed

3 files changed

+86
-37
lines changed

cluster_framework/dcp_replicator.cc

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,8 @@ void DcpReplicatorImpl::createPipesForNode(const Cluster& cluster,
182182
cb::mcbp::Feature::XERROR,
183183
cb::mcbp::Feature::SELECT_BUCKET,
184184
cb::mcbp::Feature::SNAPPY,
185-
cb::mcbp::Feature::JSON}};
185+
cb::mcbp::Feature::JSON,
186+
cb::mcbp::Feature::Collections}};
186187

187188
for (size_t node = 0; node < vbids.size(); ++node) {
188189
if (vbids[node].empty()) {
@@ -216,7 +217,8 @@ void DcpReplicatorImpl::createPipeForNodes(const Cluster& cluster,
216217
cb::mcbp::Feature::XERROR,
217218
cb::mcbp::Feature::SELECT_BUCKET,
218219
cb::mcbp::Feature::SNAPPY,
219-
cb::mcbp::Feature::JSON}};
220+
cb::mcbp::Feature::JSON,
221+
cb::mcbp::Feature::Collections}};
220222

221223
if (vbids[specific.consumer].empty()) {
222224
// I don't have any connections towards this node
@@ -240,7 +242,12 @@ void DcpReplicatorImpl::createDcpPipe(const Cluster& cluster,
240242

241243
// Create and send a DCP open
242244
auto rsp = connection->execute(BinprotDcpOpenCommand{
243-
name, cb::mcbp::request::DcpOpenPayload::Producer});
245+
name,
246+
cb::mcbp::request::DcpOpenPayload::Producer |
247+
cb::mcbp::request::DcpOpenPayload::IncludeXattrs |
248+
cb::mcbp::request::DcpOpenPayload::
249+
IncludeDeletedUserXattrs |
250+
cb::mcbp::request::DcpOpenPayload::IncludeDeleteTimes});
244251
if (!rsp.isSuccess()) {
245252
throw std::runtime_error(
246253
"DcpReplicatorImpl::start: Failed to set up "

daemon/protocol/mcbp/dcp_deletion.cc

Lines changed: 38 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@
1616
#include <mcbp/protocol/request.h>
1717
#include <memcached/limits.h>
1818
#include <memcached/protocol_binary.h>
19+
#include <xattr/blob.h>
20+
21+
static bool invalidXattrSize(cb::const_byte_buffer value,
22+
protocol_binary_datatype_t datatype) {
23+
const char* payload = reinterpret_cast<const char*>(value.data());
24+
cb::xattr::Blob blob({const_cast<char*>(payload), value.size()},
25+
mcbp::datatype::is_snappy(datatype));
26+
return blob.get_system_size() > cb::limits::PrivilegedBytes;
27+
}
1928

2029
static cb::engine_errc dcp_deletion_v1_executor(Cookie& cookie) {
2130
auto& request = cookie.getHeader().getRequest();
@@ -41,24 +50,22 @@ static cb::engine_errc dcp_deletion_v1_executor(Cookie& cookie) {
4150
cb::const_byte_buffer meta{value.data() + value.size() - nmeta, nmeta};
4251
value = {value.data(), value.size() - nmeta};
4352

44-
uint32_t priv_bytes = 0;
45-
if (mcbp::datatype::is_xattr(datatype)) {
46-
priv_bytes = gsl::narrow<uint32_t>(value.size());
47-
}
48-
if (priv_bytes <= cb::limits::PrivilegedBytes) {
49-
return dcpDeletion(cookie,
50-
opaque,
51-
key,
52-
value,
53-
priv_bytes,
54-
datatype,
55-
cas,
56-
vbucket,
57-
by_seqno,
58-
rev_seqno,
59-
meta);
53+
if (mcbp::datatype::is_xattr(datatype) &&
54+
invalidXattrSize(value, datatype)) {
55+
return cb::engine_errc::too_big;
6056
}
61-
return cb::engine_errc::too_big;
57+
58+
return dcpDeletion(cookie,
59+
opaque,
60+
key,
61+
value,
62+
0, // @todo: remove priv_bytes. unused.
63+
datatype,
64+
cas,
65+
vbucket,
66+
by_seqno,
67+
rev_seqno,
68+
meta);
6269
}
6370

6471
// The updated deletion sends no extended meta, but does send a deletion time
@@ -86,25 +93,22 @@ static cb::engine_errc dcp_deletion_v2_executor(Cookie& cookie) {
8693
const uint32_t delete_time = payload.getDeleteTime();
8794

8895
auto value = request.getValue();
89-
uint32_t priv_bytes = 0;
90-
if (mcbp::datatype::is_xattr(datatype)) {
91-
priv_bytes = gsl::narrow<uint32_t>(value.size());
96+
if (mcbp::datatype::is_xattr(datatype) &&
97+
invalidXattrSize(value, datatype)) {
98+
return cb::engine_errc::too_big;
9299
}
93100

94-
if (priv_bytes <= cb::limits::PrivilegedBytes) {
95-
return dcpDeletionV2(cookie,
96-
opaque,
97-
key,
98-
value,
99-
priv_bytes,
100-
datatype,
101-
cas,
102-
vbucket,
103-
by_seqno,
104-
rev_seqno,
105-
delete_time);
106-
}
107-
return cb::engine_errc::too_big;
101+
return dcpDeletionV2(cookie,
102+
opaque,
103+
key,
104+
value,
105+
0, // @todo: remove this unused parameter
106+
datatype,
107+
cas,
108+
vbucket,
109+
by_seqno,
110+
rev_seqno,
111+
delete_time);
108112
}
109113

110114
void dcp_deletion_executor(Cookie& cookie) {

tests/testapp_cluster/durability_tests.cc

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
#include <cluster_framework/bucket.h>
1414
#include <cluster_framework/cluster.h>
15+
#include <memcached/limits.h>
1516
#include <protocol/connection/client_connection.h>
1617
#include <protocol/connection/client_mcbp_commands.h>
1718
#include <protocol/connection/frameinfo.h>
@@ -246,6 +247,43 @@ TEST_F(DurabilityTest, SyncWriteReviveDeletedDocument) {
246247
EXPECT_EQ(cb::mcbp::Status::Success, resp.getStatus());
247248
}
248249

250+
// Verify that we can create (and replicate) a large xattr, exceed the PrivBytes
251+
TEST_F(DurabilityTest, CreateAsDeletedLarge) {
252+
BinprotSubdocMultiMutationCommand cmd;
253+
std::string name = "CreateAsDeletedLarge";
254+
// Ensure we exceed priv bytes. Just using this size is enough when we add
255+
// the key
256+
std::string value(cb::limits::PrivilegedBytes, 'v');
257+
cmd.setKey(name);
258+
cmd.addDocFlag(mcbp::subdoc::doc_flag::Add);
259+
cmd.addDocFlag(mcbp::subdoc::doc_flag::CreateAsDeleted);
260+
cmd.addMutation(cb::mcbp::ClientOpcode::SubdocDictUpsert,
261+
SUBDOC_FLAG_XATTR_PATH | SUBDOC_FLAG_MKDIR_P,
262+
"tnx.foo",
263+
"\"" + value + "\"");
264+
auto conn = getConnection();
265+
conn->sendCommand(cmd);
266+
267+
BinprotSubdocMultiMutationResponse resp;
268+
conn->recvResponse(resp);
269+
ASSERT_EQ(cb::mcbp::Status::SubdocSuccessDeleted, resp.getStatus());
270+
271+
// Do a sync-write after to ensure the test replicates the previous big
272+
// xattr
273+
cmd = {};
274+
cmd.setKey(name);
275+
cmd.addDocFlag(mcbp::subdoc::doc_flag::AccessDeleted);
276+
cmd.addFrameInfo(DurabilityFrameInfo{cb::durability::Level::Majority});
277+
cmd.addDocFlag(mcbp::subdoc::doc_flag::ReviveDocument);
278+
cmd.addMutation(cb::mcbp::ClientOpcode::SubdocDictUpsert,
279+
SUBDOC_FLAG_XATTR_PATH,
280+
"tnx.bar",
281+
R"("This should succeed")");
282+
conn->sendCommand(cmd);
283+
conn->recvResponse(resp);
284+
EXPECT_EQ(cb::mcbp::Status::Success, resp.getStatus());
285+
}
286+
249287
/**
250288
* MB-34780 - Bucket delete fails if we've got pending sync writes
251289
*

0 commit comments

Comments
 (0)