tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

dbmshim.c (14288B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 /*
      6 * Berkeley DB 1.85 Shim code to handle blobs.
      7 */
      8 #include "mcom_db.h"
      9 #include "secitem.h"
     10 #include "nssb64.h"
     11 #include "blapi.h"
     12 #include "secerr.h"
     13 
     14 #include "lgdb.h"
     15 
     16 /*
     17 *   Blob block:
     18 *   Byte 0   CERTDB Version           -+                       -+
     19 *   Byte 1   certDBEntryTypeBlob       |  BLOB_HEAD_LEN         |
     20 *   Byte 2   flags (always '0');       |                        |
     21 *   Byte 3   reserved (always '0');   -+                        |
     22 *   Byte 4   LSB length                | <--BLOB_LENGTH_START   | BLOB_BUF_LEN
     23 *   Byte 5       .                     |                        |
     24 *   Byte 6       .                     | BLOB_LENGTH_LEN        |
     25 *   Byte 7   MSB length                |                        |
     26 *   Byte 8   blob_filename   -+       -+  <-- BLOB_NAME_START   |
     27 *   Byte 9       .            | BLOB_NAME_LEN                   |
     28 *     .          .            |                                 |
     29 *   Byte 37      .           -+                                -+
     30 */
     31 #define DBS_BLOCK_SIZE (16 * 1024)                   /* 16 k */
     32 #define DBS_MAX_ENTRY_SIZE (DBS_BLOCK_SIZE - (2048)) /* 14 k */
     33 #define DBS_CACHE_SIZE DBS_BLOCK_SIZE * 8
     34 #define ROUNDDIV(x, y) (x + (y - 1)) / y
     35 #define BLOB_HEAD_LEN 4
     36 #define BLOB_LENGTH_START BLOB_HEAD_LEN
     37 #define BLOB_LENGTH_LEN 4
     38 #define BLOB_NAME_START BLOB_LENGTH_START + BLOB_LENGTH_LEN
     39 #define BLOB_NAME_LEN 1 + ROUNDDIV(SHA1_LENGTH, 3) * 4 + 1
     40 #define BLOB_BUF_LEN BLOB_HEAD_LEN + BLOB_LENGTH_LEN + BLOB_NAME_LEN
     41 
     42 /* a Shim data structure. This data structure has a db built into it. */
     43 typedef struct DBSStr DBS;
     44 
     45 struct DBSStr {
     46    DB db;
     47    char *blobdir;
     48    int mode;
     49    PRBool readOnly;
     50    char staticBlobArea[BLOB_BUF_LEN];
     51 };
     52 
     53 /*
     54 * return true if the Datablock contains a blobtype
     55 */
     56 static PRBool
     57 dbs_IsBlob(DBT *blobData)
     58 {
     59    unsigned char *addr = (unsigned char *)blobData->data;
     60    if (blobData->size < BLOB_BUF_LEN) {
     61        return PR_FALSE;
     62    }
     63    return addr && ((certDBEntryType)addr[1] == certDBEntryTypeBlob);
     64 }
     65 
     66 /*
     67 * extract the filename in the blob of the real data set.
     68 * This value is not malloced (does not need to be freed by the caller.
     69 */
     70 static const char *
     71 dbs_getBlobFileName(DBT *blobData)
     72 {
     73    char *addr = (char *)blobData->data;
     74 
     75    return &addr[BLOB_NAME_START];
     76 }
     77 
     78 /*
     79 * extract the size of the actual blob from the blob record
     80 */
     81 static PRUint32
     82 dbs_getBlobSize(DBT *blobData)
     83 {
     84    unsigned char *addr = (unsigned char *)blobData->data;
     85 
     86    return (PRUint32)(addr[BLOB_LENGTH_START + 3] << 24) |
     87           (addr[BLOB_LENGTH_START + 2] << 16) |
     88           (addr[BLOB_LENGTH_START + 1] << 8) |
     89           addr[BLOB_LENGTH_START];
     90 }
     91 
     92 /* We are using base64 data for the filename, but base64 data can include a
     93 * '/' which is interpreted as a path separator on  many platforms. Replace it
     94 * with an inocuous '-'. We don't need to convert back because we never actual
     95 * decode the filename.
     96 */
     97 
     98 static void
     99 dbs_replaceSlash(char *cp, int len)
    100 {
    101    while (len--) {
    102        if (*cp == '/')
    103            *cp = '-';
    104        cp++;
    105    }
    106 }
    107 
    108 /*
    109 * create a blob record from a key, data and return it in blobData.
    110 * NOTE: The data element is static data (keeping with the dbm model).
    111 */
    112 static void
    113 dbs_mkBlob(DBS *dbsp, const DBT *key, const DBT *data, DBT *blobData)
    114 {
    115    unsigned char sha1_data[SHA1_LENGTH];
    116    char *b = dbsp->staticBlobArea;
    117    PRUint32 length = data->size;
    118    SECItem sha1Item;
    119 
    120    b[0] = CERT_DB_FILE_VERSION;      /* certdb version number */
    121    b[1] = (char)certDBEntryTypeBlob; /* type */
    122    b[2] = 0;                         /* flags */
    123    b[3] = 0;                         /* reserved */
    124    b[BLOB_LENGTH_START] = length & 0xff;
    125    b[BLOB_LENGTH_START + 1] = (length >> 8) & 0xff;
    126    b[BLOB_LENGTH_START + 2] = (length >> 16) & 0xff;
    127    b[BLOB_LENGTH_START + 3] = (length >> 24) & 0xff;
    128    sha1Item.data = sha1_data;
    129    sha1Item.len = SHA1_LENGTH;
    130    SHA1_HashBuf(sha1_data, key->data, key->size);
    131    b[BLOB_NAME_START] = 'b'; /* Make sure we start with a alpha */
    132    NSSBase64_EncodeItem(NULL, &b[BLOB_NAME_START + 1], BLOB_NAME_LEN - 1, &sha1Item);
    133    b[BLOB_BUF_LEN - 1] = 0;
    134    dbs_replaceSlash(&b[BLOB_NAME_START + 1], BLOB_NAME_LEN - 1);
    135    blobData->data = b;
    136    blobData->size = BLOB_BUF_LEN;
    137    return;
    138 }
    139 
    140 /*
    141 * construct a path to the actual blob. The string returned must be
    142 * freed by the caller with PR_smprintf_free.
    143 *
    144 * Note: this file does lots of consistancy checks on the DBT. The
    145 * routines that call this depend on these checks, so they don't worry
    146 * about them (success of this routine implies a good blobdata record).
    147 */
    148 static char *
    149 dbs_getBlobFilePath(char *blobdir, DBT *blobData)
    150 {
    151    const char *name;
    152 
    153    if (blobdir == NULL) {
    154        PR_SetError(SEC_ERROR_BAD_DATABASE, 0);
    155        return NULL;
    156    }
    157    if (!dbs_IsBlob(blobData)) {
    158        PR_SetError(SEC_ERROR_BAD_DATABASE, 0);
    159        return NULL;
    160    }
    161    name = dbs_getBlobFileName(blobData);
    162    if (!name || *name == 0) {
    163        PR_SetError(SEC_ERROR_BAD_DATABASE, 0);
    164        return NULL;
    165    }
    166    return PR_smprintf("%s" PATH_SEPARATOR "%s", blobdir, name);
    167 }
    168 
    169 /*
    170 * Delete a blob file pointed to by the blob record.
    171 */
    172 static void
    173 dbs_removeBlob(DBS *dbsp, DBT *blobData)
    174 {
    175    char *file;
    176 
    177    file = dbs_getBlobFilePath(dbsp->blobdir, blobData);
    178    if (!file) {
    179        return;
    180    }
    181    PR_Delete(file);
    182    PR_smprintf_free(file);
    183 }
    184 
    185 /*
    186 * Directory modes are slightly different, the 'x' bit needs to be on to
    187 * access them. Copy all the read bits to 'x' bits
    188 */
    189 static int
    190 dbs_DirMode(int mode)
    191 {
    192    int x_bits = (mode >> 2) & 0111;
    193    return mode | x_bits;
    194 }
    195 
    196 /*
    197 * write a data blob to it's file. blobdData is the blob record that will be
    198 * stored in the database. data is the actual data to go out on disk.
    199 */
    200 static int
    201 dbs_writeBlob(DBS *dbsp, int mode, DBT *blobData, const DBT *data)
    202 {
    203    char *file = NULL;
    204    PRFileDesc *filed;
    205    PRStatus status;
    206    int len;
    207    int error = 0;
    208 
    209    file = dbs_getBlobFilePath(dbsp->blobdir, blobData);
    210    if (!file) {
    211        goto loser;
    212    }
    213    if (PR_Access(dbsp->blobdir, PR_ACCESS_EXISTS) != PR_SUCCESS) {
    214        status = PR_MkDir(dbsp->blobdir, dbs_DirMode(mode));
    215        if (status != PR_SUCCESS) {
    216            goto loser;
    217        }
    218    }
    219    filed = PR_OpenFile(file, PR_CREATE_FILE | PR_TRUNCATE | PR_WRONLY, mode);
    220    if (filed == NULL) {
    221        error = PR_GetError();
    222        goto loser;
    223    }
    224    len = PR_Write(filed, data->data, data->size);
    225    error = PR_GetError();
    226    PR_Close(filed);
    227    if (len < (int)data->size) {
    228        goto loser;
    229    }
    230    PR_smprintf_free(file);
    231    return 0;
    232 
    233 loser:
    234    if (file) {
    235        PR_Delete(file);
    236        PR_smprintf_free(file);
    237    }
    238    /* don't let close or delete reset the error */
    239    PR_SetError(error, 0);
    240    return -1;
    241 }
    242 
    243 /*
    244 * platforms that cannot map the file need to read it into a temp buffer.
    245 */
    246 static unsigned char *
    247 dbs_EmulateMap(PRFileDesc *filed, int len)
    248 {
    249    unsigned char *addr;
    250    PRInt32 dataRead;
    251 
    252    addr = PORT_Alloc(len);
    253    if (addr == NULL) {
    254        return NULL;
    255    }
    256 
    257    dataRead = PR_Read(filed, addr, len);
    258    if (dataRead != len) {
    259        PORT_Free(addr);
    260        if (dataRead > 0) {
    261            /* PR_Read didn't set an error, we need to */
    262            PR_SetError(SEC_ERROR_BAD_DATABASE, 0);
    263        }
    264        return NULL;
    265    }
    266 
    267    return addr;
    268 }
    269 
    270 /*
    271 * pull a database record off the disk
    272 * data points to the blob record on input and the real record (if we could
    273 * read it) on output. if there is an error data is not modified.
    274 */
    275 static int
    276 dbs_readBlob(DBS *dbsp, DBT *data)
    277 {
    278    char *file = NULL;
    279    PRFileDesc *filed = NULL;
    280    unsigned char *addr = NULL;
    281    int error;
    282    int len = -1;
    283 
    284    file = dbs_getBlobFilePath(dbsp->blobdir, data);
    285    if (!file) {
    286        goto loser;
    287    }
    288    filed = PR_OpenFile(file, PR_RDONLY, 0);
    289    PR_smprintf_free(file);
    290    file = NULL;
    291    if (filed == NULL) {
    292        goto loser;
    293    }
    294 
    295    len = dbs_getBlobSize(data);
    296    /* Bug 1323150
    297     * PR_MemMap fails on Windows for larger certificates.
    298     * https://msdn.microsoft.com/en-us/library/windows/desktop/aa366761(v=vs.85).aspx
    299     * Let's always use the emulated map, i.e. read the file.
    300     */
    301    addr = dbs_EmulateMap(filed, len);
    302    if (addr == NULL) {
    303        goto loser;
    304    }
    305    PR_Close(filed);
    306 
    307    data->data = addr;
    308    data->size = len;
    309    return 0;
    310 
    311 loser:
    312    /* preserve the error code */
    313    error = PR_GetError();
    314    if (filed) {
    315        PR_Close(filed);
    316    }
    317    PR_SetError(error, 0);
    318    return -1;
    319 }
    320 
    321 /*
    322 * actual DBM shims
    323 */
    324 static int
    325 dbs_get(const DB *dbs, const DBT *key, DBT *data, unsigned int flags)
    326 {
    327    int ret;
    328    DBS *dbsp = (DBS *)dbs;
    329    DB *db = (DB *)dbs->internal;
    330 
    331    ret = (*db->get)(db, key, data, flags);
    332    if ((ret == 0) && dbs_IsBlob(data)) {
    333        ret = dbs_readBlob(dbsp, data);
    334    }
    335 
    336    return (ret);
    337 }
    338 
    339 static int
    340 dbs_put(const DB *dbs, DBT *key, const DBT *data, unsigned int flags)
    341 {
    342    DBT blob;
    343    int ret = 0;
    344    DBS *dbsp = (DBS *)dbs;
    345    DB *db = (DB *)dbs->internal;
    346 
    347    /* If the db is readonly, just pass the data down to rdb and let it fail */
    348    if (!dbsp->readOnly) {
    349        DBT oldData;
    350        int ret1;
    351 
    352        /* make sure the current record is deleted if it's a blob */
    353        ret1 = (*db->get)(db, key, &oldData, 0);
    354        if ((ret1 == 0) && flags == R_NOOVERWRITE) {
    355            /* let DBM return the error to maintain consistancy */
    356            return (*db->put)(db, key, data, flags);
    357        }
    358        if ((ret1 == 0) && dbs_IsBlob(&oldData)) {
    359            dbs_removeBlob(dbsp, &oldData);
    360        }
    361 
    362        if (data->size > DBS_MAX_ENTRY_SIZE) {
    363            dbs_mkBlob(dbsp, key, data, &blob);
    364            ret = dbs_writeBlob(dbsp, dbsp->mode, &blob, data);
    365            data = &blob;
    366        }
    367    }
    368 
    369    if (ret == 0) {
    370        ret = (*db->put)(db, key, data, flags);
    371    }
    372    return (ret);
    373 }
    374 
    375 static int
    376 dbs_sync(const DB *dbs, unsigned int flags)
    377 {
    378    DB *db = (DB *)dbs->internal;
    379    return (*db->sync)(db, flags);
    380 }
    381 
    382 static int
    383 dbs_del(const DB *dbs, const DBT *key, unsigned int flags)
    384 {
    385    int ret;
    386    DBS *dbsp = (DBS *)dbs;
    387    DB *db = (DB *)dbs->internal;
    388 
    389    if (!dbsp->readOnly) {
    390        DBT oldData;
    391        ret = (*db->get)(db, key, &oldData, 0);
    392        if ((ret == 0) && dbs_IsBlob(&oldData)) {
    393            dbs_removeBlob(dbsp, &oldData);
    394        }
    395    }
    396 
    397    return (*db->del)(db, key, flags);
    398 }
    399 
    400 static int
    401 dbs_seq(const DB *dbs, DBT *key, DBT *data, unsigned int flags)
    402 {
    403    int ret;
    404    DBS *dbsp = (DBS *)dbs;
    405    DB *db = (DB *)dbs->internal;
    406 
    407    ret = (*db->seq)(db, key, data, flags);
    408    if ((ret == 0) && dbs_IsBlob(data)) {
    409        /* don't return a blob read as an error so traversals keep going */
    410        (void)dbs_readBlob(dbsp, data);
    411    }
    412 
    413    return (ret);
    414 }
    415 
    416 static int
    417 dbs_close(DB *dbs)
    418 {
    419    DBS *dbsp = (DBS *)dbs;
    420    DB *db = (DB *)dbs->internal;
    421    int ret;
    422 
    423    ret = (*db->close)(db);
    424    PORT_Free(dbsp->blobdir);
    425    PORT_Free(dbsp);
    426    return ret;
    427 }
    428 
    429 static int
    430 dbs_fd(const DB *dbs)
    431 {
    432    DB *db = (DB *)dbs->internal;
    433 
    434    return (*db->fd)(db);
    435 }
    436 
    437 /*
    438 * the naming convention we use is
    439 * change the .xxx into .dir. (for nss it's always .db);
    440 * if no .extension exists or is equal to .dir, add a .dir
    441 * the returned data must be freed.
    442 */
    443 #define DIRSUFFIX ".dir"
    444 static char *
    445 dbs_mkBlobDirName(const char *dbname)
    446 {
    447    int dbname_len = PORT_Strlen(dbname);
    448    int dbname_end = dbname_len;
    449    const char *cp;
    450    char *blobDir = NULL;
    451 
    452    /* scan back from the end looking for either a directory separator, a '.',
    453     * or the end of the string. NOTE: Windows should check for both separators
    454     * here. For now this is safe because we know NSS always uses a '.'
    455     */
    456    for (cp = &dbname[dbname_len];
    457         (cp > dbname) && (*cp != '.') && (*cp != *PATH_SEPARATOR);
    458         cp--)
    459        /* Empty */;
    460    if (*cp == '.') {
    461        dbname_end = cp - dbname;
    462        if (PORT_Strcmp(cp, DIRSUFFIX) == 0) {
    463            dbname_end = dbname_len;
    464        }
    465    }
    466    blobDir = PORT_ZAlloc(dbname_end + sizeof(DIRSUFFIX));
    467    if (blobDir == NULL) {
    468        return NULL;
    469    }
    470    PORT_Memcpy(blobDir, dbname, dbname_end);
    471    PORT_Memcpy(&blobDir[dbname_end], DIRSUFFIX, sizeof(DIRSUFFIX));
    472    return blobDir;
    473 }
    474 
    475 #define DBM_DEFAULT 0
    476 static const HASHINFO dbs_hashInfo = {
    477    DBS_BLOCK_SIZE, /* bucket size, must be greater than = to
    478                     * or maximum entry size (+ header)
    479                     * we allow before blobing */
    480    DBM_DEFAULT,    /* Fill Factor */
    481    DBM_DEFAULT,    /* number of elements */
    482    DBS_CACHE_SIZE, /* cache size */
    483    DBM_DEFAULT,    /* hash function */
    484    DBM_DEFAULT,    /* byte order */
    485 };
    486 
    487 /*
    488 * the open function. NOTE: this is the only exposed function in this file.
    489 * everything else is called through the function table pointer.
    490 */
    491 DB *
    492 dbsopen(const char *dbname, int flags, int mode, DBTYPE type,
    493        const void *userData)
    494 {
    495    DB *db = NULL, *dbs = NULL;
    496    DBS *dbsp = NULL;
    497 
    498    /* NOTE: we are overriding userData with dbs_hashInfo. since all known
    499     * callers pass 0, this is ok, otherwise we should merge the two */
    500 
    501    dbsp = (DBS *)PORT_ZAlloc(sizeof(DBS));
    502    if (!dbsp) {
    503        return NULL;
    504    }
    505    dbs = &dbsp->db;
    506 
    507    dbsp->blobdir = dbs_mkBlobDirName(dbname);
    508    if (dbsp->blobdir == NULL) {
    509        goto loser;
    510    }
    511    dbsp->mode = mode;
    512    dbsp->readOnly = (PRBool)(flags == NO_RDONLY);
    513 
    514    /* the real dbm call */
    515    db = dbopen(dbname, flags, mode, type, &dbs_hashInfo);
    516    if (db == NULL) {
    517        goto loser;
    518    }
    519    dbs->internal = (void *)db;
    520    dbs->type = type;
    521    dbs->close = dbs_close;
    522    dbs->get = dbs_get;
    523    dbs->del = dbs_del;
    524    dbs->put = dbs_put;
    525    dbs->seq = dbs_seq;
    526    dbs->sync = dbs_sync;
    527    dbs->fd = dbs_fd;
    528 
    529    return dbs;
    530 loser:
    531    if (db) {
    532        (*db->close)(db);
    533    }
    534    if (dbsp->blobdir) {
    535        PORT_Free(dbsp->blobdir);
    536    }
    537    PORT_Free(dbsp);
    538    return NULL;
    539 }