Skip to content

Commit cf41d0a

Browse files
committed
CDRIVER-5506 error if mongoc_gridfs_file_readv reads an incomplete chunk (#1565)
* include more info in error message * add regression test for reading incomplete chunk * CDRIVER-5506 error if reading an incomplete chunk Fixes a possible hang when `mongoc_gridfs_file_readv` is called with a corrupt interior chunk sized smaller than `chunk_size`. Implementation assumes chunks (excluding the last chunk) are of size `chunk_size` for calcluating offsets.
1 parent e69dbac commit cf41d0a

File tree

3 files changed

+174
-4
lines changed

3 files changed

+174
-4
lines changed

NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ Cyrus SASL:
55

66
* Disable plugin loading with Cyrus SASL on Windows by default. To re-enable, set the CMake option `CYRUS_PLUGIN_PATH_PREFIX` to the absolute path prefix of the Cyrus SASL plugins.
77

8+
Fixes:
9+
10+
* Fix possible hang if `mongoc_gridfs_file_readv` is called with a corrupt chunk with incomplete data.
11+
812
libmongoc 1.26.1
913
================
1014

src/libmongoc/src/mongoc/mongoc-gridfs-file.c

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -872,6 +872,18 @@ _mongoc_gridfs_file_refresh_page (mongoc_gridfs_file_t *file)
872872
}
873873
} else if (strcmp (key, "data") == 0) {
874874
bson_iter_binary (&iter, NULL, &len, &data);
875+
// If this not the last chunk, ensure length is equal to chunk size.
876+
bool is_last_chunk = ((file->n + 1) == existing_chunks);
877+
// If this is not the last chunk, error.
878+
if (!is_last_chunk && bson_cmp_not_equal_us (len, file->chunk_size)) {
879+
bson_set_error (&file->error,
880+
MONGOC_ERROR_GRIDFS,
881+
MONGOC_ERROR_GRIDFS_CORRUPT,
882+
"corrupt chunk number %" PRId32 ": not equal to chunk size: %" PRId32,
883+
file->n,
884+
file->chunk_size);
885+
RETURN (0);
886+
}
875887
} else {
876888
/* Unexpected key. This should never happen */
877889
RETURN (0);
@@ -887,7 +899,7 @@ _mongoc_gridfs_file_refresh_page (mongoc_gridfs_file_t *file)
887899
bson_set_error (&file->error,
888900
MONGOC_ERROR_GRIDFS,
889901
MONGOC_ERROR_GRIDFS_CHUNK_MISSING,
890-
"corrupt chunk number %" PRId32,
902+
"corrupt chunk number %" PRId32 ": no data",
891903
file->n);
892904
RETURN (0);
893905
}
@@ -896,8 +908,9 @@ _mongoc_gridfs_file_refresh_page (mongoc_gridfs_file_t *file)
896908
bson_set_error (&file->error,
897909
MONGOC_ERROR_GRIDFS,
898910
MONGOC_ERROR_GRIDFS_CORRUPT,
899-
"corrupt chunk number %" PRId32 ": bad size",
900-
file->n);
911+
"corrupt chunk number %" PRId32 ": greater than chunk size: %" PRId32,
912+
file->n,
913+
file->chunk_size);
901914
RETURN (0);
902915
}
903916

src/libmongoc/tests/test-mongoc-gridfs.c

Lines changed: 154 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1301,7 +1301,7 @@ test_oversize (void)
13011301
ASSERT_ERROR_CONTAINS (error,
13021302
MONGOC_ERROR_GRIDFS,
13031303
MONGOC_ERROR_GRIDFS_CORRUPT,
1304-
"corrupt chunk number 0: bad size");
1304+
"corrupt chunk number 0: greater than chunk size");
13051305

13061306
mongoc_gridfs_file_destroy (file);
13071307
ASSERT_OR_PRINT (mongoc_gridfs_drop (gridfs, &error), error);
@@ -1584,6 +1584,156 @@ test_write_failure (void)
15841584
mock_server_destroy (server);
15851585
}
15861586

1587+
static void
1588+
test_reading_multiple_chunks (void)
1589+
{
1590+
mongoc_client_t *client = test_framework_new_default_client ();
1591+
bson_error_t error;
1592+
1593+
// Test reading a file spanning two chunks.
1594+
{
1595+
mongoc_gridfs_t *gridfs = mongoc_client_get_gridfs (
1596+
client, "test_reading_multiple_chunks", NULL, &error);
1597+
1598+
ASSERT_OR_PRINT (gridfs, error);
1599+
// Drop prior test data.
1600+
ASSERT_OR_PRINT (mongoc_gridfs_drop (gridfs, &error), error);
1601+
1602+
// Write a file spanning two chunks.
1603+
{
1604+
mongoc_gridfs_file_opt_t opts = {.chunk_size = 4,
1605+
.filename = "test_file"};
1606+
mongoc_iovec_t iov = {.iov_base = (void *) "foobar", .iov_len = 7};
1607+
mongoc_gridfs_file_t *file = mongoc_gridfs_create_file (gridfs, &opts);
1608+
// First chunk is 4 bytes: "foob", second chunk is 3 bytes: "ar\0"
1609+
ASSERT_CMPSSIZE_T (
1610+
mongoc_gridfs_file_writev (file, &iov, 1, 0), ==, 7);
1611+
BSON_ASSERT (mongoc_gridfs_file_save (file));
1612+
mongoc_gridfs_file_destroy (file);
1613+
}
1614+
1615+
// Read the entire file.
1616+
{
1617+
bson_string_t *str = bson_string_new ("");
1618+
uint8_t buf[7] = {0};
1619+
mongoc_iovec_t iov = {.iov_base = (void *) buf,
1620+
.iov_len = sizeof (buf)};
1621+
mongoc_gridfs_file_t *file =
1622+
mongoc_gridfs_find_one_by_filename (gridfs, "test_file", &error);
1623+
ASSERT_OR_PRINT (file, error);
1624+
1625+
// First read gets first chunk.
1626+
{
1627+
ssize_t got = mongoc_gridfs_file_readv (file,
1628+
&iov,
1629+
1 /* iovcnt */,
1630+
1 /* min_bytes */,
1631+
0 /* timeout_msec */);
1632+
ASSERT_CMPSSIZE_T (got, >=, 0);
1633+
ASSERT (bson_in_range_int_signed (got));
1634+
bson_string_append_printf (str, "%.*s", (int) got, (char *) buf);
1635+
ASSERT_CMPSSIZE_T (got, ==, 4);
1636+
}
1637+
1638+
// Second read gets second chunk.
1639+
{
1640+
ssize_t got = mongoc_gridfs_file_readv (file,
1641+
&iov,
1642+
1 /* iovcnt */,
1643+
1 /* min_bytes */,
1644+
0 /* timeout_msec */);
1645+
ASSERT_CMPSSIZE_T (got, >=, 0);
1646+
ASSERT (bson_in_range_int_signed (got));
1647+
bson_string_append_printf (str, "%.*s", (int) got, (char *) buf);
1648+
ASSERT_CMPSSIZE_T (got, ==, 3);
1649+
}
1650+
1651+
ASSERT_CMPSTR (str->str, "foobar");
1652+
bson_string_free (str, true);
1653+
mongoc_gridfs_file_destroy (file);
1654+
}
1655+
1656+
mongoc_gridfs_destroy (gridfs);
1657+
}
1658+
1659+
// Test an error occurs if reading an incomplete chunk. This is a regression
1660+
// test for CDRIVER-5506.
1661+
{
1662+
mongoc_gridfs_t *gridfs = mongoc_client_get_gridfs (
1663+
client, "test_reading_multiple_chunks", NULL, &error);
1664+
1665+
ASSERT_OR_PRINT (gridfs, error);
1666+
// Drop prior test data.
1667+
ASSERT_OR_PRINT (mongoc_gridfs_drop (gridfs, &error), error);
1668+
1669+
// Write a file spanning two chunks.
1670+
{
1671+
mongoc_gridfs_file_opt_t opts = {.chunk_size = 4,
1672+
.filename = "test_file"};
1673+
mongoc_iovec_t iov = {.iov_base = (void *) "foobar", .iov_len = 7};
1674+
mongoc_gridfs_file_t *file = mongoc_gridfs_create_file (gridfs, &opts);
1675+
// First chunk is 4 bytes: "foob", second chunk is 3 bytes: "ar\0"
1676+
ASSERT_CMPSSIZE_T (
1677+
mongoc_gridfs_file_writev (file, &iov, 1, 0), ==, 7);
1678+
BSON_ASSERT (mongoc_gridfs_file_save (file));
1679+
mongoc_gridfs_file_destroy (file);
1680+
}
1681+
1682+
// Manually remove data from the first chunk.
1683+
{
1684+
mongoc_collection_t *coll = mongoc_client_get_collection (
1685+
client, "test_reading_multiple_chunks", "fs.chunks");
1686+
bson_t reply;
1687+
// Change the data of the first chunk from "foob" to "foo".
1688+
bool ok = mongoc_collection_update_one (
1689+
coll,
1690+
tmp_bson (BSON_STR ({"n" : 0})),
1691+
tmp_bson (BSON_STR ({
1692+
"$set" :
1693+
{"data" : {"$binary" : {"base64" : "Zm9v", "subType" : "0"}}}
1694+
})),
1695+
NULL /* opts */,
1696+
&reply,
1697+
&error);
1698+
ASSERT_OR_PRINT (ok, error);
1699+
ASSERT_MATCH (&reply, BSON_STR ({"modifiedCount" : 1}));
1700+
mongoc_collection_destroy (coll);
1701+
}
1702+
1703+
// Attempt to read the entire file.
1704+
{
1705+
uint8_t buf[7] = {0};
1706+
mongoc_iovec_t iov = {.iov_base = (void *) buf,
1707+
.iov_len = sizeof (buf)};
1708+
mongoc_gridfs_file_t *file =
1709+
mongoc_gridfs_find_one_by_filename (gridfs, "test_file", &error);
1710+
ASSERT_OR_PRINT (file, error);
1711+
1712+
// First read gets an error.
1713+
{
1714+
ssize_t got = mongoc_gridfs_file_readv (file,
1715+
&iov,
1716+
1 /* iovcnt */,
1717+
1 /* min_bytes */,
1718+
0 /* timeout_msec */);
1719+
ASSERT_CMPSSIZE_T (got, ==, -1);
1720+
ASSERT (mongoc_gridfs_file_error (file, &error));
1721+
ASSERT_ERROR_CONTAINS (
1722+
error,
1723+
MONGOC_ERROR_GRIDFS,
1724+
MONGOC_ERROR_GRIDFS_CORRUPT,
1725+
"corrupt chunk number 0: not equal to chunk size: 4");
1726+
}
1727+
1728+
mongoc_gridfs_file_destroy (file);
1729+
}
1730+
1731+
mongoc_gridfs_destroy (gridfs);
1732+
}
1733+
1734+
mongoc_client_destroy (client);
1735+
}
1736+
15871737

15881738
void
15891739
test_gridfs_install (TestSuite *suite)
@@ -1628,4 +1778,7 @@ test_gridfs_install (TestSuite *suite)
16281778
suite, "/gridfs_old/inherit_client_config", test_inherit_client_config);
16291779
TestSuite_AddMockServerTest (
16301780
suite, "/gridfs_old/write_failure", test_write_failure);
1781+
TestSuite_AddLive (suite,
1782+
"/gridfs_old/reading_multiple_chunks",
1783+
test_reading_multiple_chunks);
16311784
}

0 commit comments

Comments
 (0)