[CDRIVER-2701] Add example of validating BSON nesting depth with bson_visitor_t Created: 15/Jun/18  Updated: 28/Oct/23  Resolved: 22/Oct/18

Status: Closed
Project: C Driver
Component/s: docs, libmongoc
Affects Version/s: 1.9.4
Fix Version/s: 1.14.0

Type: Improvement Priority: Minor - P4
Reporter: Bh Sr Assignee: Kevin Albertson
Resolution: Fixed Votes: 0
Labels: crash
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified
Environment:

Windows 10


Issue Links:
Related
is related to CDRIVER-2814 No simple way to validate a BSON docu... Closed

 Description   

Original title: "Calling mongoc_collection_insert with a document which contains hundreds of levels of nested BSON documents causes stack overflow"

I know that MongoDB only supports up to 100 levels of nesting for BSON documents but if someone unknowingly calls mongoc_collection_insert() and passes a document with >500 levels of nesting, then it causes stack overflow.

 

The overflow happens while validating the BSON document. If I disable the validation by passing in MONGOC_INSERT_NO_VALIDATE, then of course, it doesn't crash.

 

I understand that this is unsupported behavior, but it would be nice if it didn't cause stack overflow.



 Comments   
Comment by Githook User [ 22/Oct/18 ]

Author:

{'name': 'Kevin Albertson', 'email': 'kevin.albertson@10gen.com', 'username': 'kevinAlbs'}

Message: CDRIVER-2701 update docs for bson validate
Branch: master
https://github.com/mongodb/mongo-c-driver/commit/415119c30226366223195d4bc5c8230bdd1a54cc

Comment by A. Jesse Jiryu Davis [ 19/Oct/18 ]

Let's put the code example that I wrote here into the documentation for bson_visitor_t, and link to it from the bson_validate docs as well.

Comment by Bh Sr [ 05/Sep/18 ]

I create a separate ticket CDRIVER-2814 to keep the discussion on validation.

Comment by A. Jesse Jiryu Davis [ 31/Aug/18 ]

Yes, you could use the BSON visitor API, something like this sketch:

#include <mongoc.h>
#include <stdio.h>
#include <assert.h>
 
typedef struct {
   bool valid;
   uint32_t depth;
} check_depth_t;
 
bool
_check_depth_document (const bson_iter_t *iter,
                       const char *key,
                       const bson_t *v_document,
                       void *data);
 
static const bson_visitor_t check_depth_funcs = {
   NULL,
   NULL,
   NULL,
   NULL,
   NULL,
   _check_depth_document,
   _check_depth_document,
   NULL,
};
 
bool
_check_depth_document (const bson_iter_t *iter,
                       const char *key,
                       const bson_t *v_document,
                       void *data)
{
   check_depth_t *state = (check_depth_t *) data;
   bson_iter_t child;
 
   if (!bson_iter_init (&child, v_document)) {
      fprintf (stderr, "corrupt\n");
      state->valid = false;
      return true; /* cancel */
   }
 
   if (state->depth >= 100) {
      fprintf (stderr, "too deep\n");
      state->valid = false;
      return true; /* cancel */
   }
 
   state->depth++;
   bson_iter_visit_all (&child, &check_depth_funcs, state);
   state->depth--;
 
   return false; /* continue */
}
 
bool
check_depth (const bson_t *bson)
{
   bson_iter_t iter;
   check_depth_t state;
   state.valid = true;
   state.depth = 0;
 
   if (!bson_iter_init (&iter, bson)) {
      fprintf (stderr, "corrupt\n");
      return false;
   }
 
   _check_depth_document (&iter, NULL, bson, &state);
   return state.valid;
}
 
int
main ()
{
   bson_t levels[101];
   int i;
   bson_t *parent, *child;
 
   bson_init (&levels[0]);
 
   /* call append_document_begin 100 times: */
   for (i = 1; i < sizeof (levels) / sizeof (bson_t); i++) {
      parent = &levels[i - 1];
      child = &levels[i];
 
      BSON_ASSERT (bson_append_document_begin (parent, "a", 1, child));
   }
 
   while (i > 1) {
      i--;
      parent = &levels[i - 1];
      child = &levels[i];
      bson_append_document_end (parent, child);
   }
 
   /* levels[1] points to a document with 100 levels, which is valid */
   assert (check_depth (&levels[1]));
   /* levels[0] points that document's parent, a document with 101 levels,
    * which is forbidden */
   assert (!check_depth (&levels[0]));
   bson_destroy (&levels[0]);
 
   return 0;
}

Use a function something like that to check your inputs before passing them to C Driver functions like mongoc_collection_insert.

Comment by Bh Sr [ 29/Aug/18 ]

I understand your concerns. But is there any way to validate a BSON document in libbson without crashing if it contains a deeply nested document? In other words, if we get a BSON document by our users and we want to check if it's a valid document, is there any way to do it without crashing? 

Comment by A. Jesse Jiryu Davis [ 06/Aug/18 ]

Hi, on second thought, let's not implement any special checking in the C Driver.

The MongoDB server already has logic for testing the nesting level of an inserted document. It uses a maximum depth of 180, in BSONDepth::getMaxDepthForUserStorage(). The Extended JSON spec says that MongoDB supports 100 levels; although this is incorrect, most drivers' JSON-to-BSON codecs are implemented on this assumption.

If the C Driver's bson_validate() enforces a nesting limit it will be inconsistent with MongoDB's actual behavior, or inconsistent with the Extended JSON Spec, or both. It's better to enforce no limit at all.

I understand that in situations like yours this means that very deeply nested BSON crashes your program instead of returning an error, but it seems overall to be less risky to leave the code as it is.

Comment by A. Jesse Jiryu Davis [ 15/Jun/18 ]

Thanks for the report!

Generated at Wed Feb 07 21:16:04 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.