Prevent potential overruns of fixed-size buffers.
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 17 Feb 2014 16:20:38 +0000 (11:20 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 17 Feb 2014 16:20:38 +0000 (11:20 -0500)
Coverity identified a number of places in which it couldn't prove that a
string being copied into a fixed-size buffer would fit.  We believe that
most, perhaps all of these are in fact safe, or are copying data that is
coming from a trusted source so that any overrun is not really a security
issue.  Nonetheless it seems prudent to forestall any risk by using
strlcpy() and similar functions.

Fixes by Peter Eisentraut and Jozef Mlich based on Coverity reports.

In addition, fix a potential null-pointer-dereference crash in
contrib/chkpass.  The crypt(3) function is defined to return NULL on
failure, but chkpass.c didn't check for that before using the result.
The main practical case in which this could be an issue is if libc is
configured to refuse to execute unapproved hashing algorithms (e.g.,
"FIPS mode").  This ideally should've been a separate commit, but
since it touches code adjacent to one of the buffer overrun changes,
I included it in this commit to avoid last-minute merge issues.
This issue was reported by Honza Horak.

Security: CVE-2014-0065 for buffer overruns, CVE-2014-0066 for crypt()

contrib/chkpass/chkpass.c
contrib/pg_standby/pg_standby.c
src/backend/tsearch/spell.c
src/backend/utils/adt/datetime.c
src/bin/initdb/initdb.c
src/interfaces/ecpg/preproc/pgc.l
src/interfaces/libpq/fe-protocol2.c
src/interfaces/libpq/fe-protocol3.c
src/port/exec.c
src/test/regress/pg_regress.c
src/timezone/pgtz.c

index 56a998ec839cc5d2469fe99e6c88b60e8b7a695e..b54aacca52c3ac200e7d64c8292b0f67299f5e38 100644 (file)
@@ -70,6 +70,7 @@ chkpass_in(PG_FUNCTION_ARGS)
    char       *str = PG_GETARG_CSTRING(0);
    chkpass    *result;
    char        mysalt[4];
+   char       *crypt_output;
    static char salt_chars[] =
    "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
 
@@ -92,7 +93,15 @@ chkpass_in(PG_FUNCTION_ARGS)
    mysalt[1] = salt_chars[random() & 0x3f];
    mysalt[2] = 0;              /* technically the terminator is not necessary
                                 * but I like to play safe */
-   strcpy(result->password, crypt(str, mysalt));
+
+   crypt_output = crypt(str, mysalt);
+   if (crypt_output == NULL)
+       ereport(ERROR,
+               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                errmsg("crypt() failed")));
+
+   strlcpy(result->password, crypt_output, sizeof(result->password));
+
    PG_RETURN_POINTER(result);
 }
 
@@ -141,9 +150,16 @@ chkpass_eq(PG_FUNCTION_ARGS)
    chkpass    *a1 = (chkpass *) PG_GETARG_POINTER(0);
    text       *a2 = PG_GETARG_TEXT_PP(1);
    char        str[9];
+   char       *crypt_output;
 
    text_to_cstring_buffer(a2, str, sizeof(str));
-   PG_RETURN_BOOL(strcmp(a1->password, crypt(str, a1->password)) == 0);
+   crypt_output = crypt(str, a1->password);
+   if (crypt_output == NULL)
+       ereport(ERROR,
+               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                errmsg("crypt() failed")));
+
+   PG_RETURN_BOOL(strcmp(a1->password, crypt_output) == 0);
 }
 
 PG_FUNCTION_INFO_V1(chkpass_ne);
@@ -153,7 +169,14 @@ chkpass_ne(PG_FUNCTION_ARGS)
    chkpass    *a1 = (chkpass *) PG_GETARG_POINTER(0);
    text       *a2 = PG_GETARG_TEXT_PP(1);
    char        str[9];
+   char       *crypt_output;
 
    text_to_cstring_buffer(a2, str, sizeof(str));
-   PG_RETURN_BOOL(strcmp(a1->password, crypt(str, a1->password)) != 0);
+   crypt_output = crypt(str, a1->password);
+   if (crypt_output == NULL)
+       ereport(ERROR,
+               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                errmsg("crypt() failed")));
+
+   PG_RETURN_BOOL(strcmp(a1->password, crypt_output) != 0);
 }
index 508dedf86097179f7213569ed95cca1a500686d7..d889c5cc64352ae3a6fe1b5a03d2afae8f621c7a 100644 (file)
@@ -337,7 +337,7 @@ SetWALFileNameForCleanup(void)
        if (strcmp(restartWALFileName, nextWALFileName) > 0)
            return false;
 
-       strcpy(exclusiveCleanupFileName, restartWALFileName);
+       strlcpy(exclusiveCleanupFileName, restartWALFileName, sizeof(exclusiveCleanupFileName));
        return true;
    }
 
index 71a3d94001f45cc0f8838999d1bfc505fa8ed83d..5c5a629f6f1538d4d573e7078105b9495868d7f1 100644 (file)
@@ -181,7 +181,7 @@ NIAddSpell(IspellDict *Conf, const char *word, const char *flag)
    }
    Conf->Spell[Conf->nspell] = (SPELL *) tmpalloc(SPELLHDRSZ + strlen(word) + 1);
    strcpy(Conf->Spell[Conf->nspell]->word, word);
-   strncpy(Conf->Spell[Conf->nspell]->p.flag, flag, MAXFLAGLEN);
+   strlcpy(Conf->Spell[Conf->nspell]->p.flag, flag, MAXFLAGLEN);
    Conf->nspell++;
 }
 
index bf63e4dc8feb8ea0ca3e531454855ab98c771b01..22878627cc6814b40820492e41406f5d732edaa5 100644 (file)
@@ -89,10 +89,10 @@ char       *days[] = {"Sunday", "Monday", "Tuesday", "Wednesday",
  * Note that this table must be strictly alphabetically ordered to allow an
  * O(ln(N)) search algorithm to be used.
  *
- * The text field is NOT guaranteed to be NULL-terminated.
+ * The token field is NOT guaranteed to be NULL-terminated.
  *
- * To keep this table reasonably small, we divide the lexval for TZ and DTZ
- * entries by 15 (so they are on 15 minute boundaries) and truncate the text
+ * To keep this table reasonably small, we divide the value for TZ and DTZ
+ * entries by 15 (so they are on 15 minute boundaries) and truncate the token
  * field at TOKMAXLEN characters.
  * Formerly, we divided by 10 rather than 15 but there are a few time zones
  * which are 30 or 45 minutes away from an even hour, most are on an hour
@@ -107,7 +107,7 @@ static datetkn *timezonetktbl = NULL;
 static int sztimezonetktbl = 0;
 
 static const datetkn datetktbl[] = {
-/* text, token, lexval */
+   /* token, type, value */
    {EARLY, RESERV, DTK_EARLY}, /* "-infinity" reserved for "early time" */
    {DA_D, ADBC, AD},           /* "ad" for years > 0 */
    {"allballs", RESERV, DTK_ZULU},     /* 00:00:00 */
@@ -187,7 +187,7 @@ static const datetkn datetktbl[] = {
 static int szdatetktbl = sizeof datetktbl / sizeof datetktbl[0];
 
 static datetkn deltatktbl[] = {
-   /* text, token, lexval */
+   /* token, type, value */
    {"@", IGNORE_DTF, 0},       /* postgres relative prefix */
    {DAGO, AGO, 0},             /* "ago" indicates negative time offset */
    {"c", UNITS, DTK_CENTURY},  /* "century" relative */
@@ -4145,6 +4145,7 @@ InstallTimeZoneAbbrevs(tzEntry *abbrevs, int n)
                                            n * sizeof(datetkn));
    for (i = 0; i < n; i++)
    {
+       /* do NOT use strlcpy here; token field need not be null-terminated */
        strncpy(newtbl[i].token, abbrevs[i].abbrev, TOKMAXLEN);
        newtbl[i].type = abbrevs[i].is_dst ? DTZ : TZ;
        TOVAL(&newtbl[i], abbrevs[i].offset / 60);
index a0b816112765f5258a215d862b47975dc89b1764..d5a2902a1a1a240eca30df2c5db012c299ed750f 100644 (file)
@@ -3174,7 +3174,7 @@ main(int argc, char *argv[])
        fprintf(stderr, "%s", authwarning);
 
    /* Get directory specification used to start this executable */
-   strcpy(bin_dir, argv[0]);
+   strlcpy(bin_dir, argv[0], sizeof(bin_dir));
    get_parent_directory(bin_dir);
 
    printf(_("\nSuccess. You can now start the database server using:\n\n"
index 8de155555f12e87e4c6a3afe394bd8d1efb16224..d63e5a7aedb3bbd5053c0a969c14f1363a9a07b6 100644 (file)
@@ -1270,7 +1270,7 @@ parse_include(void)
        yytext[i] = '\0';
        memmove(yytext, yytext+1, strlen(yytext));
    
-       strncpy(inc_file, yytext, sizeof(inc_file));
+       strlcpy(inc_file, yytext, sizeof(inc_file));
        yyin = fopen(inc_file, "r");
        if (!yyin)
        {
index 88c6b520f5021885f8e8a7d99d42c9fef0df20f5..b99268c6064cc627792b77a87ac13ec40fca552c 100644 (file)
@@ -440,7 +440,7 @@ pqParseInput2(PGconn *conn)
                        if (!conn->result)
                            return;
                    }
-                   strncpy(conn->result->cmdStatus, conn->workBuffer.data,
+                   strlcpy(conn->result->cmdStatus, conn->workBuffer.data,
                            CMDSTATUS_LEN);
                    checkXactStatus(conn, conn->workBuffer.data);
                    conn->asyncStatus = PGASYNC_READY;
index 10e5edd756ca205c61605d6eb97de2bb36be6b4d..7131b87ea4532a39abf11ff6737162f15dc376d0 100644 (file)
@@ -206,7 +206,7 @@ pqParseInput3(PGconn *conn)
                        if (!conn->result)
                            return;
                    }
-                   strncpy(conn->result->cmdStatus, conn->workBuffer.data,
+                   strlcpy(conn->result->cmdStatus, conn->workBuffer.data,
                            CMDSTATUS_LEN);
                    conn->asyncStatus = PGASYNC_READY;
                    break;
index 52484fa38e76d52db3215690f24f1830b7f2c176..e92fdee020422bceb7e94675d4c596c5bef9b9ae 100644 (file)
@@ -88,7 +88,7 @@ validate_exec(const char *path)
    if (strlen(path) >= strlen(".exe") &&
        pg_strcasecmp(path + strlen(path) - strlen(".exe"), ".exe") != 0)
    {
-       strcpy(path_exe, path);
+       strlcpy(path_exe, path, sizeof(path_exe) - 4);
        strcat(path_exe, ".exe");
        path = path_exe;
    }
@@ -345,7 +345,7 @@ resolve_symlinks(char *path)
    }
 
    /* must copy final component out of 'path' temporarily */
-   strcpy(link_buf, fname);
+   strlcpy(link_buf, fname, sizeof(link_buf));
 
    if (!getcwd(path, MAXPGPATH))
    {
index eb014a27d1307740b0c21ec4b8a736c6cec769cc..8c30813b002cc298a79c110eaf3c9cee0477189e 100644 (file)
@@ -1227,7 +1227,7 @@ results_differ(const char *testname, const char *resultsfile, const char *defaul
     */
    platform_expectfile = get_expectfile(testname, resultsfile);
 
-   strcpy(expectfile, default_expectfile);
+   strlcpy(expectfile, default_expectfile, sizeof(expectfile));
    if (platform_expectfile)
    {
        /*
@@ -1282,7 +1282,7 @@ results_differ(const char *testname, const char *resultsfile, const char *defaul
        {
            /* This diff was a better match than the last one */
            best_line_count = l;
-           strcpy(best_expect_file, alt_expectfile);
+           strlcpy(best_expect_file, alt_expectfile, sizeof(best_expect_file));
        }
        free(alt_expectfile);
    }
@@ -1310,7 +1310,7 @@ results_differ(const char *testname, const char *resultsfile, const char *defaul
        {
            /* This diff was a better match than the last one */
            best_line_count = l;
-           strcpy(best_expect_file, default_expectfile);
+           strlcpy(best_expect_file, default_expectfile, sizeof(best_expect_file));
        }
    }
 
index e72184c6ff343a734c199d2887ae5eb610f38364..d584f97166c9943d7f269c030fc892ef757361ed 100644 (file)
@@ -94,7 +94,7 @@ pg_open_tzfile(const char *name, char *canonname)
     * Loop to split the given name into directory levels; for each level,
     * search using scan_directory_ci().
     */
-   strcpy(fullname, pg_TZDIR());
+   strlcpy(fullname, pg_TZDIR(), sizeof(fullname));
    orignamelen = fullnamelen = strlen(fullname);
    fname = name;
    for (;;)
@@ -428,7 +428,7 @@ identify_system_timezone(void)
    }
 
    /* Search for the best-matching timezone file */
-   strcpy(tmptzdir, pg_TZDIR());
+   strlcpy(tmptzdir, pg_TZDIR(), sizeof(tmptzdir));
    bestscore = -1;
    resultbuf[0] = '\0';
    scan_available_timezones(tmptzdir, tmptzdir + strlen(tmptzdir) + 1,