Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit be79deb

Browse files
committed
Fix out-of-bound memory access for interval -> char conversion
Using Roman numbers (via "RM" or "rm") for a conversion to calculate a number of months has never considered the case of negative numbers, where a conversion could easily cause out-of-bound memory accesses. The conversions in themselves were not completely consistent either, as specifying 12 would result in NULL, but it should mean XII. This commit reworks the conversion calculation to have a more consistent behavior: - If the number of months and years is 0, return NULL. - If the number of months is positive, return the exact month number. - If the number of months is negative, do a backward calculation, with -1 meaning December, -2 November, etc. Reported-by: Theodor Arsenij Larionov-Trichkin Author: Julien Rouhaud Discussion: https://postgr.es/m/16953-f255a18f8c51f1d5@postgresql.org backpatch-through: 9.6
1 parent 19b28d6 commit be79deb

File tree

3 files changed

+95
-10
lines changed

3 files changed

+95
-10
lines changed

src/backend/utils/adt/formatting.c

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3208,18 +3208,61 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
32083208
s += strlen(s);
32093209
break;
32103210
case DCH_RM:
3211-
if (!tm->tm_mon)
3212-
break;
3213-
sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -4,
3214-
rm_months_upper[MONTHS_PER_YEAR - tm->tm_mon]);
3215-
s += strlen(s);
3216-
break;
3211+
/* FALLTHROUGH */
32173212
case DCH_rm:
3218-
if (!tm->tm_mon)
3213+
3214+
/*
3215+
* For intervals, values like '12 month' will be reduced to 0
3216+
* month and some years. These should be processed.
3217+
*/
3218+
if (!tm->tm_mon && !tm->tm_year)
32193219
break;
3220-
sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -4,
3221-
rm_months_lower[MONTHS_PER_YEAR - tm->tm_mon]);
3222-
s += strlen(s);
3220+
else
3221+
{
3222+
int mon = 0;
3223+
const char *const *months;
3224+
3225+
if (n->key->id == DCH_RM)
3226+
months = rm_months_upper;
3227+
else
3228+
months = rm_months_lower;
3229+
3230+
/*
3231+
* Compute the position in the roman-numeral array. Note
3232+
* that the contents of the array are reversed, December
3233+
* being first and January last.
3234+
*/
3235+
if (tm->tm_mon == 0)
3236+
{
3237+
/*
3238+
* This case is special, and tracks the case of full
3239+
* interval years.
3240+
*/
3241+
mon = tm->tm_year >= 0 ? 0 : MONTHS_PER_YEAR - 1;
3242+
}
3243+
else if (tm->tm_mon < 0)
3244+
{
3245+
/*
3246+
* Negative case. In this case, the calculation is
3247+
* reversed, where -1 means December, -2 November,
3248+
* etc.
3249+
*/
3250+
mon = -1 * (tm->tm_mon + 1);
3251+
}
3252+
else
3253+
{
3254+
/*
3255+
* Common case, with a strictly positive value. The
3256+
* position in the array matches with the value of
3257+
* tm_mon.
3258+
*/
3259+
mon = MONTHS_PER_YEAR - tm->tm_mon;
3260+
}
3261+
3262+
sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -4,
3263+
months[mon]);
3264+
s += strlen(s);
3265+
}
32233266
break;
32243267
case DCH_W:
32253268
sprintf(s, "%d", (tm->tm_mday - 1) / 7 + 1);

src/test/regress/expected/timestamp.out

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1703,6 +1703,42 @@ SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff
17031703
| 7 78 789 7890 78901 789012 7 78 789 7890 78901 789012 789 789012
17041704
(4 rows)
17051705

1706+
-- Roman months, with upper and lower case.
1707+
SELECT i,
1708+
to_char(i * interval '1mon', 'rm'),
1709+
to_char(i * interval '1mon', 'RM')
1710+
FROM generate_series(-13, 13) i;
1711+
i | to_char | to_char
1712+
-----+---------+---------
1713+
-13 | xii | XII
1714+
-12 | i | I
1715+
-11 | ii | II
1716+
-10 | iii | III
1717+
-9 | iv | IV
1718+
-8 | v | V
1719+
-7 | vi | VI
1720+
-6 | vii | VII
1721+
-5 | viii | VIII
1722+
-4 | ix | IX
1723+
-3 | x | X
1724+
-2 | xi | XI
1725+
-1 | xii | XII
1726+
0 | |
1727+
1 | i | I
1728+
2 | ii | II
1729+
3 | iii | III
1730+
4 | iv | IV
1731+
5 | v | V
1732+
6 | vi | VI
1733+
7 | vii | VII
1734+
8 | viii | VIII
1735+
9 | ix | IX
1736+
10 | x | X
1737+
11 | xi | XI
1738+
12 | xii | XII
1739+
13 | i | I
1740+
(27 rows)
1741+
17061742
-- timestamp numeric fields constructor
17071743
SELECT make_timestamp(2014,12,28,6,30,45.887);
17081744
make_timestamp

src/test/regress/sql/timestamp.sql

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,5 +239,11 @@ SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff
239239
('2018-11-02 12:34:56.78901234')
240240
) d(d);
241241

242+
-- Roman months, with upper and lower case.
243+
SELECT i,
244+
to_char(i * interval '1mon', 'rm'),
245+
to_char(i * interval '1mon', 'RM')
246+
FROM generate_series(-13, 13) i;
247+
242248
-- timestamp numeric fields constructor
243249
SELECT make_timestamp(2014,12,28,6,30,45.887);

0 commit comments

Comments
 (0)