Hi,
I was working on the lmdb-go bindings for LMDB, using the 0.9.19 release and believe I have found a bug with MDB_MULTIPLE. The bug still exists on the latest commit of branch mdb.RE/0.9, a87c8fd8c9a4ce49be18a642e3572059f39ed1cf, I'm not sure if this bug is part of what held the 0.9.20 release back. But I wanted to check here.
I noticed that, for in a corner case of the mdb_cursor_put function when MDB_MULTIPLE is provided, if the number of items is specified to be zero this may lead to garbage data being written or a segfault. My intuition would be that this is essentially a noop, but I also would have thought EINVAL would be an acceptable error code for this strange use of mdb_cursor_put. The actual behavior of LMDB does not seem acceptable.
I've included an barebones example which I have written in C to demonstrate the problem. Please let me know what you think.
- Bryan
Here is my program's output
$ ./mdb_putmultiple_empty -V LMDB 0.9.20: (January 11, 2017) $ ./mdb_putmultiple_empty -n putmultiple_empty.mdb && echo ok Segmentation fault (core dumped)
And, here is the program source
/* mdb_putmultiple_empty.c - test of MDB_PUT_MULTIPLE given zero items*/ #ifdef _WIN32 #include <windows.h> #endif #include <stdio.h> #include <stdlib.h> #include <signal.h> #include "lmdb.h"
static void sighandle(int sig) { }
int main(int argc,char * argv[]) { int rc; MDB_env *env; MDB_txn *txn; MDB_cursor *cursor; MDB_dbi dbi; MDB_val vals[3]; const char *progname = argv[0], *act; unsigned flags = 0;
for (; argc > 1 && argv[1][0] == '-'; argc--, argv++) { if (argv[1][1] == 'n' && argv[1][2] == '\0') flags |= MDB_NOSUBDIR; else if (argv[1][1] == 'V' && argv[1][2] == '\0') { printf("%s\n", MDB_VERSION_STRING); exit(0); } else argc = 0; }
if (argc<2 || argc>3) { fprintf(stderr, "usage: %s [-V] [-n] srcpath\n", progname); exit(EXIT_FAILURE); }
#ifdef SIGPIPE signal(SIGPIPE, sighandle); #endif #ifdef SIGHUP signal(SIGHUP, sighandle); #endif signal(SIGINT, sighandle); signal(SIGTERM, sighandle);
act = "creating environment"; rc = mdb_env_create(&env); if (rc == MDB_SUCCESS) { act = "setting maxdbs"; rc = mdb_env_set_maxdbs(env, 1); } if (rc == MDB_SUCCESS) { act = "opening environment"; rc = mdb_env_open(env, argv[1], flags, 0600); }
if (rc == MDB_SUCCESS) { act = "beginning txn"; rc = mdb_txn_begin(env, 0,0, &txn); } if (rc == MDB_SUCCESS) { act = "opening dbi"; rc = mdb_dbi_open(txn, "db", MDB_CREATE|MDB_DUPSORT|MDB_DUPFIXED, &dbi); } if (rc == MDB_SUCCESS) { act = "opening cursor"; rc = mdb_cursor_open(txn, dbi, &cursor); } if (rc == MDB_SUCCESS) { act = "put multiple with zero items but non-empty stride"; vals[0].mv_size = 1; vals[0].mv_data = "x";
vals[1].mv_size = 2; vals[1].mv_data = 0; vals[2].mv_size = 0; vals[2].mv_data = 0;
rc = mdb_cursor_put(cursor, &vals[0], &vals[1], MDB_MULTIPLE); } if (rc == MDB_SUCCESS) { act = "put multiple with zero items but non-empty stride"; vals[0].mv_size = 0; vals[0].mv_data = 0; vals[1].mv_size = 0; vals[1].mv_data = 0; rc = mdb_cursor_get(cursor, &vals[0], &vals[1], MDB_FIRST); } if (rc == MDB_SUCCESS) { fprintf(stderr, "FIRST: %.*s %.*s", (int)vals[0].mv_size, (char*)vals[0].mv_data, (int)vals[1].mv_size, (char*)vals[1].mv_data); }
if (rc) fprintf(stderr, "%s: %s failed, error %d (%s)\n", progname, act, rc, mdb_strerror(rc)); mdb_txn_abort(txn); mdb_env_close(env);
return rc ? EXIT_FAILURE : EXIT_SUCCESS; }