Off-by-One Error Memory Corruption
A while ago, a user of the PHP Library for MongoDB reported a bug stating:
There is a special find query that fails with the message: Client Error: bad object in message: bson length doesn't match what we found in object with unknown _id
One can change the characters but not the length of strings and obtain the same exception.
Over the past week I had a look at this, after this was filed as PHPC-1067 for the MongoDB PHP driver, once we found that it actually causes a crash. When running the script from the report, I indeed found a Segmentation Fault (read: crash), where I noticed that during the course of a few function calls an unrelated C variable changed. The change was in a bson_t* type, which the driver uses for variables that go in and out of the MongoDB C client library. This data type has two implementations: an inline version, used for most small BSON documents; and an allocated version that can be used for building BSON structures, and which is typically used for larger documents.
In the driver's function, the data types are declared as:
bson_t bdocument = BSON_INITIALIZER, boptions = BSON_INITIALIZER; bson_t *bson_out = NULL;
With the affected code being:
php_phongo_zval_to_bson(zdocument, bson_flags, &bdocument, &bson_out TSRMLS_CC)
zdocument is the zval structure that PHP uses for storing variables, in this case, the document sent into the method. bdocument receives the created BSON document, boptions are options to go with the insertion, and bson_out receives the _id field that is either present in the original zdocument document, or that was generated in case no _id field was present. MongoDB documents are required to have this _id field and bson_out is used to be able to return the generated _id field so that you can do follow-up queries with it.
However, after php_phongo_zval_to_bson was run, parts of the structure of boptions changed in such a way that an internal flag now showed it was an allocated bson_t* instead of the inline version it really was. This meant that when freeing it later in the code through bson_destroy(&boptions) a memory free was attempted on non-allocated memory, which then of course caused a crash in form of the Segmentation Fault. The weird thing is that the change happened in a variable that should not have been touched by the call to php_phongo_zval_to_bson.
With the reason of the crash found, it was now time to find the cause of the crash. Which proved to be a little trickier.
Normally, with any sort of memory issues the first thing I would do is to wield Valgrind as it usually tells were things go wrong. I wrote about Valgrind before. But in this case, it didn't show anything besides that the process tried to free not-allocated memory:
==9002== Invalid read of size 8 ==9002== at 0xC7CBDA6: bson_destroy (bson.c:2285) ==9002== by 0xC862155: zim_BulkWrite_insert (BulkWrite.c:246) ==9002== by 0x9D64E2: execute_internal (zend_execute.c:2077) ==9002== by 0xB8690CC: xdebug_execute_internal (xdebug.c:2020) ==9002== Address 0x0 is not stack'd, malloc'd or (recently) free'd
And we already knew that. After looking, and stepping through the code with GDB for way too long, I had a revelation while relaxing on the weekend that the bdocument and boptions were not actually allocated variables, but instead just variables on the stack. And Valgrind's memcheck tool does not have ways to actually detect this.
Having these variables allocated on the stack is a performance improvement, as there is no overhead of memory allocation, but while trying to find a bug, we don't care much about it being fast. So I converted these few lines of code to use an allocated version, to see if I could get some more useful information out of Valgrind:
bson_t bdocument = BSON_INITIALIZER, boptions = BSON_INITIALIZER; php_phongo_zval_to_bson(zdocument, bson_flags, &bdocument, &bson_out TSRMLS_CC);
to:
bson_t *bdocument, *boptions; bdocument = bson_new(); boptions = bson_new(); php_phongo_zval_to_bson(zdocument, bson_flags, bdocument, &bson_out TSRMLS_CC);
With that changed, the Valgrind run now showed:
Invalid write of size 1 at 0xC7C72FA: _bson_append_va (bson.c:348) by 0xC7C74DB: _bson_append (bson.c:404) by 0xC7C9DA1: bson_append_regex (bson.c:1575) by 0xC85122D: php_phongo_bson_append_object (bson-encode.c:234) by 0xC851C57: php_phongo_bson_append (bson-encode.c:382) by 0xC85230E: php_phongo_zval_to_bson (bson-encode.c:540) by 0xC862047: zim_BulkWrite_insert (BulkWrite.c:229) Address 0xd8c9540 is 0 bytes after a block of size 128 alloc'd at 0x4C2CBEF: malloc (vg_replace_malloc.c:299) by 0x940C00: __zend_malloc (zend_alloc.c:2829) by 0xC84DBCF: php_phongo_malloc (php_phongo.c:2485) by 0xC7DD388: bson_malloc (bson-memory.c:68) by 0xC7CB170: bson_new (bson.c:2015) by 0xC861F94: zim_BulkWrite_insert (BulkWrite.c:216)
It says invalid write of size 1 and 0 bytes after a block of size
128 alloc'd. This quickly illustrated that it was trying to do a write outside of its allocated memory area. Adding elements to the BSON document, in this case a regular expression through bson_append_regex, should normally grow the allocated memory when it runs out of already allocated space. Knowing this, combined with the phrasing One can change the characters but not the length of strings and obtain the same exception. from the original bug report, hinted towards an improper calculation of the amount of new data being written to the allocated memory buffer.
I dove into the bson_append_regex code and found the spot where it builds up all the elements to add. I have annotated it a little:
r = _bson_append (bson,
5, // Number of data elements to add
// number of bytes to add
(1 + key_length + 1 + regex_len + options_sorted->len),
1, // length of first element (BSON type, int8)
&type, // the BSON type
key_length, // the length of the field name
key, // the field name (not 0-termined)
1, // the length of the ending 0 byte
&gZero, // the null 0 byte
regex_len, // the length of the regular expression (including 0 byte)
regex, // the regular expression with 0 byte
options_sorted->len + 1, // the length of the sorted options, with 0 byte
options_sorted->str); // the sorted options, with 0 byte
When comparing the "bytes" to add to the sum of the 5 "length" fields, I noticed that although an extra byte was added for options_sorted->len on the second to last line, that was not done in the calculated size on the third line.
This code got recently changed as part of CDRIVER-2128. Unfortunately a bug sneaked in miscalculating the length of the now sorted options. After adding the extra byte back in, the bug disappeared:
- (1 + key_length + 1 + regex_len + options_sorted->len), + (1 + key_length + 1 + regex_len + options_sorted->len + 1),
I filed this bug as CDRIVER-2455, and made a pull request.
Additional note: I now have found Valgrind's SGCheck tool which should assist in finding stack related memory errors. Unfortunately, this tool currently seems to be inoperative on my platform.
Life Line
I've finished reading This Way Up. It's about maps, that went wrong.
It's a good read, but htyerr were several chapters that were written in a novel way (as a video transcript, a series of letters), and I found distracting from the a tail content. It'll have worked better in a produced video.
No mention of @openstreetmap though :-(
Updated a bench
Created a tree; Updated 3 humps and a waste_basket
The Early Cormorant Catches the Eel
Sorry, not the best photo! But I caught this Cormorant catching this large eel when looking for Bank Swallows, right next to Eel Pie Island in the Thames.
#Birds #BirdPhotography #BirdsOfMastodon #Photography #London
Updated an estate_agent office
I went to my nieces' birthday party yesterday.
The theme was pink, and that included all the food, mostly died with beet root.
Shock and horror this morning when doing number two. Not only was my turd dark red, it was also glittering at me. Apparently the carrot cake had edible glitter...
So now I know what's worse than glitter.
😂 ✨ 💩 🟣Long-Tailed Tit on a Branch with Lichen
I've been spending some time in random London local nature reserves.
Sitting and listening, and in fifteen minutes you spot countless species.
This one was in Ham Lands Local Nature Reserve near Teddington.
#london #BirdPhotogaphy #BirdsOfMastodon #Birds #LichenSubscribe
A Colourful Mandarin
In The Long Water in Kensington Palace Gardens, London.
Created 7 benches
Created 2 benches
Created a bench
I walked 7.3km in 2h28m39s
Added a note about a duplicate Papersmiths
I walked 4.1km in 49m02s
Fixed website
fix typo
Updated a bench
I walked 1.6km in 20m26s
I walked 1.1km in 11m49s
The Yellow Eye
A blue heron's head, with its very yellow stare-y eye.
#BirdPhotography #Photography #BirdsOfFediverse #BirdsOfMastodon #London
My little Lego box is telling me it really is quite warm outside.
Created a bicycle_parking and a crossing
I walked 3.3km in 41m56s








Shortlink
This article has a short URL available: https://drck.me/offbyone-dwm