Patrick Williams | 2a25492 | 2023-08-11 09:48:11 -0500 | [diff] [blame] | 1 | From 3d54a41f12e9aa059f06e66e72d872f2283395b6 Mon Sep 17 00:00:00 2001 |
| 2 | From: Chen Qi <Qi.Chen@windriver.com> |
| 3 | Date: Sun, 30 Jul 2023 21:14:00 -0700 |
| 4 | Subject: [PATCH] Fix CVE-2023-29491 |
| 5 | |
| 6 | CVE: CVE-2023-29491 |
| 7 | |
| 8 | Upstream-Status: Backport [http://ncurses.scripts.mit.edu/?p=ncurses.git;a=commitdiff;h=eb51b1ea1f75a0ec17c9c5937cb28df1e8eeec56] |
| 9 | |
| 10 | Signed-off-by: Chen Qi <Qi.Chen@windriver.com> |
| 11 | --- |
| 12 | ncurses/tinfo/lib_tgoto.c | 10 +++- |
| 13 | ncurses/tinfo/lib_tparm.c | 116 ++++++++++++++++++++++++++++++++----- |
| 14 | ncurses/tinfo/read_entry.c | 3 + |
| 15 | progs/tic.c | 6 ++ |
| 16 | progs/tparm_type.c | 9 +++ |
| 17 | progs/tparm_type.h | 2 + |
| 18 | progs/tput.c | 61 ++++++++++++++++--- |
| 19 | 7 files changed, 185 insertions(+), 22 deletions(-) |
| 20 | |
| 21 | diff --git a/ncurses/tinfo/lib_tgoto.c b/ncurses/tinfo/lib_tgoto.c |
| 22 | index 9cf5e100..c50ed4df 100644 |
| 23 | --- a/ncurses/tinfo/lib_tgoto.c |
| 24 | +++ b/ncurses/tinfo/lib_tgoto.c |
| 25 | @@ -207,6 +207,14 @@ tgoto(const char *string, int x, int y) |
| 26 | result = tgoto_internal(string, x, y); |
| 27 | else |
| 28 | #endif |
| 29 | - result = TIPARM_2(string, y, x); |
| 30 | + if ((result = TIPARM_2(string, y, x)) == NULL) { |
| 31 | + /* |
| 32 | + * Because termcap did not provide a more general solution such as |
| 33 | + * tparm(), it was necessary to handle single-parameter capabilities |
| 34 | + * using tgoto(). The internal _nc_tiparm() function returns a NULL |
| 35 | + * for that case; retry for the single-parameter case. |
| 36 | + */ |
| 37 | + result = TIPARM_1(string, y); |
| 38 | + } |
| 39 | returnPtr(result); |
| 40 | } |
| 41 | diff --git a/ncurses/tinfo/lib_tparm.c b/ncurses/tinfo/lib_tparm.c |
| 42 | index d9bdfd8f..a10a3877 100644 |
| 43 | --- a/ncurses/tinfo/lib_tparm.c |
| 44 | +++ b/ncurses/tinfo/lib_tparm.c |
| 45 | @@ -1086,6 +1086,64 @@ tparam_internal(TPARM_STATE *tps, const char *string, TPARM_DATA *data) |
| 46 | return (TPS(out_buff)); |
| 47 | } |
| 48 | |
| 49 | +#ifdef CUR |
| 50 | +/* |
| 51 | + * Only a few standard capabilities accept string parameters. The others that |
| 52 | + * are parameterized accept only numeric parameters. |
| 53 | + */ |
| 54 | +static bool |
| 55 | +check_string_caps(TPARM_DATA *data, const char *string) |
| 56 | +{ |
| 57 | + bool result = FALSE; |
| 58 | + |
| 59 | +#define CHECK_CAP(name) (VALID_STRING(name) && !strcmp(name, string)) |
| 60 | + |
| 61 | + /* |
| 62 | + * Disallow string parameters unless we can check them against a terminal |
| 63 | + * description. |
| 64 | + */ |
| 65 | + if (cur_term != NULL) { |
| 66 | + int want_type = 0; |
| 67 | + |
| 68 | + if (CHECK_CAP(pkey_key)) |
| 69 | + want_type = 2; /* function key #1, type string #2 */ |
| 70 | + else if (CHECK_CAP(pkey_local)) |
| 71 | + want_type = 2; /* function key #1, execute string #2 */ |
| 72 | + else if (CHECK_CAP(pkey_xmit)) |
| 73 | + want_type = 2; /* function key #1, transmit string #2 */ |
| 74 | + else if (CHECK_CAP(plab_norm)) |
| 75 | + want_type = 2; /* label #1, show string #2 */ |
| 76 | + else if (CHECK_CAP(pkey_plab)) |
| 77 | + want_type = 6; /* function key #1, type string #2, show string #3 */ |
| 78 | +#if NCURSES_XNAMES |
| 79 | + else { |
| 80 | + char *check; |
| 81 | + |
| 82 | + check = tigetstr("Cs"); |
| 83 | + if (CHECK_CAP(check)) |
| 84 | + want_type = 1; /* style #1 */ |
| 85 | + |
| 86 | + check = tigetstr("Ms"); |
| 87 | + if (CHECK_CAP(check)) |
| 88 | + want_type = 3; /* storage unit #1, content #2 */ |
| 89 | + } |
| 90 | +#endif |
| 91 | + |
| 92 | + if (want_type == data->tparm_type) { |
| 93 | + result = TRUE; |
| 94 | + } else { |
| 95 | + T(("unexpected string-parameter")); |
| 96 | + } |
| 97 | + } |
| 98 | + return result; |
| 99 | +} |
| 100 | + |
| 101 | +#define ValidCap() (myData.tparm_type == 0 || \ |
| 102 | + check_string_caps(&myData, string)) |
| 103 | +#else |
| 104 | +#define ValidCap() 1 |
| 105 | +#endif |
| 106 | + |
| 107 | #if NCURSES_TPARM_VARARGS |
| 108 | |
| 109 | NCURSES_EXPORT(char *) |
| 110 | @@ -1100,7 +1158,7 @@ tparm(const char *string, ...) |
| 111 | tps->tname = "tparm"; |
| 112 | #endif /* TRACE */ |
| 113 | |
| 114 | - if (tparm_setup(cur_term, string, &myData) == OK) { |
| 115 | + if (tparm_setup(cur_term, string, &myData) == OK && ValidCap()) { |
| 116 | va_list ap; |
| 117 | |
| 118 | va_start(ap, string); |
| 119 | @@ -1135,7 +1193,7 @@ tparm(const char *string, |
| 120 | tps->tname = "tparm"; |
| 121 | #endif /* TRACE */ |
| 122 | |
| 123 | - if (tparm_setup(cur_term, string, &myData) == OK) { |
| 124 | + if (tparm_setup(cur_term, string, &myData) == OK && ValidCap()) { |
| 125 | |
| 126 | myData.param[0] = a1; |
| 127 | myData.param[1] = a2; |
| 128 | @@ -1166,7 +1224,7 @@ tiparm(const char *string, ...) |
| 129 | tps->tname = "tiparm"; |
| 130 | #endif /* TRACE */ |
| 131 | |
| 132 | - if (tparm_setup(cur_term, string, &myData) == OK) { |
| 133 | + if (tparm_setup(cur_term, string, &myData) == OK && ValidCap()) { |
| 134 | va_list ap; |
| 135 | |
| 136 | va_start(ap, string); |
| 137 | @@ -1179,7 +1237,25 @@ tiparm(const char *string, ...) |
| 138 | } |
| 139 | |
| 140 | /* |
| 141 | - * The internal-use flavor ensures that the parameters are numbers, not strings |
| 142 | + * The internal-use flavor ensures that parameters are numbers, not strings. |
| 143 | + * In addition to ensuring that they are numbers, it ensures that the parameter |
| 144 | + * count is consistent with intended usage. |
| 145 | + * |
| 146 | + * Unlike the general-purpose tparm/tiparm, these internal calls are fairly |
| 147 | + * well defined: |
| 148 | + * |
| 149 | + * expected == 0 - not applicable |
| 150 | + * expected == 1 - set color, or vertical/horizontal addressing |
| 151 | + * expected == 2 - cursor addressing |
| 152 | + * expected == 4 - initialize color or color pair |
| 153 | + * expected == 9 - set attributes |
| 154 | + * |
| 155 | + * Only for the last case (set attributes) should a parameter be optional. |
| 156 | + * Also, a capability which calls for more parameters than expected should be |
| 157 | + * ignored. |
| 158 | + * |
| 159 | + * Return a null if the parameter-checks fail. Otherwise, return a pointer to |
| 160 | + * the formatted capability string. |
| 161 | */ |
| 162 | NCURSES_EXPORT(char *) |
| 163 | _nc_tiparm(int expected, const char *string, ...) |
| 164 | @@ -1189,22 +1265,36 @@ _nc_tiparm(int expected, const char *string, ...) |
| 165 | char *result = NULL; |
| 166 | |
| 167 | _nc_tparm_err = 0; |
| 168 | + T((T_CALLED("_nc_tiparm(%d, %s, ...)"), expected, _nc_visbuf(string))); |
| 169 | #ifdef TRACE |
| 170 | tps->tname = "_nc_tiparm"; |
| 171 | #endif /* TRACE */ |
| 172 | |
| 173 | - if (tparm_setup(cur_term, string, &myData) == OK |
| 174 | - && myData.num_actual <= expected |
| 175 | - && myData.tparm_type == 0) { |
| 176 | - va_list ap; |
| 177 | + if (tparm_setup(cur_term, string, &myData) == OK && ValidCap()) { |
| 178 | + if (myData.num_actual == 0) { |
| 179 | + T(("missing parameter%s, expected %s%d", |
| 180 | + expected > 1 ? "s" : "", |
| 181 | + expected == 9 ? "up to " : "", |
| 182 | + expected)); |
| 183 | + } else if (myData.num_actual > expected) { |
| 184 | + T(("too many parameters, have %d, expected %d", |
| 185 | + myData.num_actual, |
| 186 | + expected)); |
| 187 | + } else if (expected != 9 && myData.num_actual != expected) { |
| 188 | + T(("expected %d parameters, have %d", |
| 189 | + myData.num_actual, |
| 190 | + expected)); |
| 191 | + } else { |
| 192 | + va_list ap; |
| 193 | |
| 194 | - va_start(ap, string); |
| 195 | - tparm_copy_valist(&myData, FALSE, ap); |
| 196 | - va_end(ap); |
| 197 | + va_start(ap, string); |
| 198 | + tparm_copy_valist(&myData, FALSE, ap); |
| 199 | + va_end(ap); |
| 200 | |
| 201 | - result = tparam_internal(tps, string, &myData); |
| 202 | + result = tparam_internal(tps, string, &myData); |
| 203 | + } |
| 204 | } |
| 205 | - return result; |
| 206 | + returnPtr(result); |
| 207 | } |
| 208 | |
| 209 | /* |
| 210 | diff --git a/ncurses/tinfo/read_entry.c b/ncurses/tinfo/read_entry.c |
| 211 | index 2b1875ed..341337d2 100644 |
| 212 | --- a/ncurses/tinfo/read_entry.c |
| 213 | +++ b/ncurses/tinfo/read_entry.c |
| 214 | @@ -323,6 +323,9 @@ _nc_read_termtype(TERMTYPE2 *ptr, char *buffer, int limit) |
| 215 | || bool_count < 0 |
| 216 | || num_count < 0 |
| 217 | || str_count < 0 |
| 218 | + || bool_count > BOOLCOUNT |
| 219 | + || num_count > NUMCOUNT |
| 220 | + || str_count > STRCOUNT |
| 221 | || str_size < 0) { |
| 222 | returnDB(TGETENT_NO); |
| 223 | } |
| 224 | diff --git a/progs/tic.c b/progs/tic.c |
| 225 | index 93a0b491..888927e2 100644 |
| 226 | --- a/progs/tic.c |
| 227 | +++ b/progs/tic.c |
| 228 | @@ -2270,9 +2270,15 @@ check_1_infotocap(const char *name, NCURSES_CONST char *value, int count) |
| 229 | |
| 230 | _nc_reset_tparm(NULL); |
| 231 | switch (actual) { |
| 232 | + case Str: |
| 233 | + result = TPARM_1(value, strings[1]); |
| 234 | + break; |
| 235 | case Num_Str: |
| 236 | result = TPARM_2(value, numbers[1], strings[2]); |
| 237 | break; |
| 238 | + case Str_Str: |
| 239 | + result = TPARM_2(value, strings[1], strings[2]); |
| 240 | + break; |
| 241 | case Num_Str_Str: |
| 242 | result = TPARM_3(value, numbers[1], strings[2], strings[3]); |
| 243 | break; |
| 244 | diff --git a/progs/tparm_type.c b/progs/tparm_type.c |
| 245 | index 3da4a077..644aa62a 100644 |
| 246 | --- a/progs/tparm_type.c |
| 247 | +++ b/progs/tparm_type.c |
| 248 | @@ -47,6 +47,7 @@ tparm_type(const char *name) |
| 249 | {code, {longname} }, \ |
| 250 | {code, {ti} }, \ |
| 251 | {code, {tc} } |
| 252 | +#define XD(code, onlyname) TD(code, onlyname, onlyname, onlyname) |
| 253 | TParams result = Numbers; |
| 254 | /* *INDENT-OFF* */ |
| 255 | static const struct { |
| 256 | @@ -58,6 +59,10 @@ tparm_type(const char *name) |
| 257 | TD(Num_Str, "pkey_xmit", "pfx", "px"), |
| 258 | TD(Num_Str, "plab_norm", "pln", "pn"), |
| 259 | TD(Num_Str_Str, "pkey_plab", "pfxl", "xl"), |
| 260 | +#if NCURSES_XNAMES |
| 261 | + XD(Str, "Cs"), |
| 262 | + XD(Str_Str, "Ms"), |
| 263 | +#endif |
| 264 | }; |
| 265 | /* *INDENT-ON* */ |
| 266 | |
| 267 | @@ -80,12 +85,16 @@ guess_tparm_type(int nparam, char **p_is_s) |
| 268 | case 1: |
| 269 | if (!p_is_s[0]) |
| 270 | result = Numbers; |
| 271 | + if (p_is_s[0]) |
| 272 | + result = Str; |
| 273 | break; |
| 274 | case 2: |
| 275 | if (!p_is_s[0] && !p_is_s[1]) |
| 276 | result = Numbers; |
| 277 | if (!p_is_s[0] && p_is_s[1]) |
| 278 | result = Num_Str; |
| 279 | + if (p_is_s[0] && p_is_s[1]) |
| 280 | + result = Str_Str; |
| 281 | break; |
| 282 | case 3: |
| 283 | if (!p_is_s[0] && !p_is_s[1] && !p_is_s[2]) |
| 284 | diff --git a/progs/tparm_type.h b/progs/tparm_type.h |
| 285 | index 7c102a30..af5bcf0f 100644 |
| 286 | --- a/progs/tparm_type.h |
| 287 | +++ b/progs/tparm_type.h |
| 288 | @@ -45,8 +45,10 @@ |
| 289 | typedef enum { |
| 290 | Other = -1 |
| 291 | ,Numbers = 0 |
| 292 | + ,Str |
| 293 | ,Num_Str |
| 294 | ,Num_Str_Str |
| 295 | + ,Str_Str |
| 296 | } TParams; |
| 297 | |
| 298 | extern TParams tparm_type(const char *name); |
| 299 | diff --git a/progs/tput.c b/progs/tput.c |
| 300 | index 4cd0c5ba..41508b72 100644 |
| 301 | --- a/progs/tput.c |
| 302 | +++ b/progs/tput.c |
| 303 | @@ -1,5 +1,5 @@ |
| 304 | /**************************************************************************** |
| 305 | - * Copyright 2018-2021,2022 Thomas E. Dickey * |
| 306 | + * Copyright 2018-2022,2023 Thomas E. Dickey * |
| 307 | * Copyright 1998-2016,2017 Free Software Foundation, Inc. * |
| 308 | * * |
| 309 | * Permission is hereby granted, free of charge, to any person obtaining a * |
| 310 | @@ -47,12 +47,15 @@ |
| 311 | #include <transform.h> |
| 312 | #include <tty_settings.h> |
| 313 | |
| 314 | -MODULE_ID("$Id: tput.c,v 1.99 2022/02/26 23:19:31 tom Exp $") |
| 315 | +MODULE_ID("$Id: tput.c,v 1.102 2023/04/08 16:26:36 tom Exp $") |
| 316 | |
| 317 | #define PUTS(s) fputs(s, stdout) |
| 318 | |
| 319 | const char *_nc_progname = "tput"; |
| 320 | |
| 321 | +static bool opt_v = FALSE; /* quiet, do not show warnings */ |
| 322 | +static bool opt_x = FALSE; /* clear scrollback if possible */ |
| 323 | + |
| 324 | static bool is_init = FALSE; |
| 325 | static bool is_reset = FALSE; |
| 326 | static bool is_clear = FALSE; |
| 327 | @@ -81,6 +84,7 @@ usage(const char *optstring) |
| 328 | KEEP(" -S << read commands from standard input") |
| 329 | KEEP(" -T TERM use this instead of $TERM") |
| 330 | KEEP(" -V print curses-version") |
| 331 | + KEEP(" -v verbose, show warnings") |
| 332 | KEEP(" -x do not try to clear scrollback") |
| 333 | KEEP("") |
| 334 | KEEP("Commands:") |
| 335 | @@ -148,7 +152,7 @@ exit_code(int token, int value) |
| 336 | * Returns nonzero on error. |
| 337 | */ |
| 338 | static int |
| 339 | -tput_cmd(int fd, TTY * settings, bool opt_x, int argc, char **argv, int *used) |
| 340 | +tput_cmd(int fd, TTY * settings, int argc, char **argv, int *used) |
| 341 | { |
| 342 | NCURSES_CONST char *name; |
| 343 | char *s; |
| 344 | @@ -231,7 +235,9 @@ tput_cmd(int fd, TTY * settings, bool opt_x, int argc, char **argv, int *used) |
| 345 | } else if (VALID_STRING(s)) { |
| 346 | if (argc > 1) { |
| 347 | int k; |
| 348 | + int narg; |
| 349 | int analyzed; |
| 350 | + int provided; |
| 351 | int popcount; |
| 352 | long numbers[1 + NUM_PARM]; |
| 353 | char *strings[1 + NUM_PARM]; |
| 354 | @@ -271,14 +277,45 @@ tput_cmd(int fd, TTY * settings, bool opt_x, int argc, char **argv, int *used) |
| 355 | |
| 356 | popcount = 0; |
| 357 | _nc_reset_tparm(NULL); |
| 358 | + /* |
| 359 | + * Count the number of numeric parameters which are provided. |
| 360 | + */ |
| 361 | + provided = 0; |
| 362 | + for (narg = 1; narg < argc; ++narg) { |
| 363 | + char *ending = NULL; |
| 364 | + long check = strtol(argv[narg], &ending, 10); |
| 365 | + if (check < 0 || ending == argv[narg] || *ending != '\0') |
| 366 | + break; |
| 367 | + provided = narg; |
| 368 | + } |
| 369 | switch (paramType) { |
| 370 | + case Str: |
| 371 | + s = TPARM_1(s, strings[1]); |
| 372 | + analyzed = 1; |
| 373 | + if (provided == 0 && argc >= 1) |
| 374 | + provided++; |
| 375 | + break; |
| 376 | + case Str_Str: |
| 377 | + s = TPARM_2(s, strings[1], strings[2]); |
| 378 | + analyzed = 2; |
| 379 | + if (provided == 0 && argc >= 1) |
| 380 | + provided++; |
| 381 | + if (provided == 1 && argc >= 2) |
| 382 | + provided++; |
| 383 | + break; |
| 384 | case Num_Str: |
| 385 | s = TPARM_2(s, numbers[1], strings[2]); |
| 386 | analyzed = 2; |
| 387 | + if (provided == 1 && argc >= 2) |
| 388 | + provided++; |
| 389 | break; |
| 390 | case Num_Str_Str: |
| 391 | s = TPARM_3(s, numbers[1], strings[2], strings[3]); |
| 392 | analyzed = 3; |
| 393 | + if (provided == 1 && argc >= 2) |
| 394 | + provided++; |
| 395 | + if (provided == 2 && argc >= 3) |
| 396 | + provided++; |
| 397 | break; |
| 398 | case Numbers: |
| 399 | analyzed = _nc_tparm_analyze(NULL, s, p_is_s, &popcount); |
| 400 | @@ -316,7 +353,13 @@ tput_cmd(int fd, TTY * settings, bool opt_x, int argc, char **argv, int *used) |
| 401 | if (analyzed < popcount) { |
| 402 | analyzed = popcount; |
| 403 | } |
| 404 | - *used += analyzed; |
| 405 | + if (opt_v && (analyzed != provided)) { |
| 406 | + fprintf(stderr, "%s: %s parameters for \"%s\"\n", |
| 407 | + _nc_progname, |
| 408 | + (analyzed < provided ? "extra" : "missing"), |
| 409 | + argv[0]); |
| 410 | + } |
| 411 | + *used += provided; |
| 412 | } |
| 413 | |
| 414 | /* use putp() in order to perform padding */ |
| 415 | @@ -339,7 +382,6 @@ main(int argc, char **argv) |
| 416 | int used; |
| 417 | TTY old_settings; |
| 418 | TTY tty_settings; |
| 419 | - bool opt_x = FALSE; /* clear scrollback if possible */ |
| 420 | bool is_alias; |
| 421 | bool need_tty; |
| 422 | |
| 423 | @@ -348,7 +390,7 @@ main(int argc, char **argv) |
| 424 | |
| 425 | term = getenv("TERM"); |
| 426 | |
| 427 | - while ((c = getopt(argc, argv, is_alias ? "T:Vx" : "ST:Vx")) != -1) { |
| 428 | + while ((c = getopt(argc, argv, is_alias ? "T:Vvx" : "ST:Vvx")) != -1) { |
| 429 | switch (c) { |
| 430 | case 'S': |
| 431 | cmdline = FALSE; |
| 432 | @@ -361,6 +403,9 @@ main(int argc, char **argv) |
| 433 | case 'V': |
| 434 | puts(curses_version()); |
| 435 | ExitProgram(EXIT_SUCCESS); |
| 436 | + case 'v': /* verbose */ |
| 437 | + opt_v = TRUE; |
| 438 | + break; |
| 439 | case 'x': /* do not try to clear scrollback */ |
| 440 | opt_x = TRUE; |
| 441 | break; |
| 442 | @@ -404,7 +449,7 @@ main(int argc, char **argv) |
| 443 | usage(NULL); |
| 444 | while (argc > 0) { |
| 445 | tty_settings = old_settings; |
| 446 | - code = tput_cmd(fd, &tty_settings, opt_x, argc, argv, &used); |
| 447 | + code = tput_cmd(fd, &tty_settings, argc, argv, &used); |
| 448 | if (code != 0) |
| 449 | break; |
| 450 | argc -= used; |
| 451 | @@ -439,7 +484,7 @@ main(int argc, char **argv) |
| 452 | while (argnum > 0) { |
| 453 | int code; |
| 454 | tty_settings = old_settings; |
| 455 | - code = tput_cmd(fd, &tty_settings, opt_x, argnum, argnow, &used); |
| 456 | + code = tput_cmd(fd, &tty_settings, argnum, argnow, &used); |
| 457 | if (code != 0) { |
| 458 | if (result == 0) |
| 459 | result = ErrSystem(0); /* will return value >4 */ |
| 460 | -- |
| 461 | 2.40.0 |
| 462 | |