From 3d54a41f12e9aa059f06e66e72d872f2283395b6 Mon Sep 17 00:00:00 2001 From: Chen Qi Date: Sun, 30 Jul 2023 21:14:00 -0700 Subject: [PATCH] Fix CVE-2023-29491 CVE: CVE-2023-29491 Upstream-Status: Backport [http://ncurses.scripts.mit.edu/?p=ncurses.git;a=commitdiff;h=eb51b1ea1f75a0ec17c9c5937cb28df1e8eeec56] Signed-off-by: Chen Qi --- ncurses/tinfo/lib_tgoto.c | 10 +++- ncurses/tinfo/lib_tparm.c | 116 ++++++++++++++++++++++++++++++++----- ncurses/tinfo/read_entry.c | 3 + progs/tic.c | 6 ++ progs/tparm_type.c | 9 +++ progs/tparm_type.h | 2 + progs/tput.c | 61 ++++++++++++++++--- 7 files changed, 185 insertions(+), 22 deletions(-) diff --git a/ncurses/tinfo/lib_tgoto.c b/ncurses/tinfo/lib_tgoto.c index 9cf5e100..c50ed4df 100644 --- a/ncurses/tinfo/lib_tgoto.c +++ b/ncurses/tinfo/lib_tgoto.c @@ -207,6 +207,14 @@ tgoto(const char *string, int x, int y) result = tgoto_internal(string, x, y); else #endif - result = TIPARM_2(string, y, x); + if ((result = TIPARM_2(string, y, x)) == NULL) { + /* + * Because termcap did not provide a more general solution such as + * tparm(), it was necessary to handle single-parameter capabilities + * using tgoto(). The internal _nc_tiparm() function returns a NULL + * for that case; retry for the single-parameter case. + */ + result = TIPARM_1(string, y); + } returnPtr(result); } diff --git a/ncurses/tinfo/lib_tparm.c b/ncurses/tinfo/lib_tparm.c index d9bdfd8f..a10a3877 100644 --- a/ncurses/tinfo/lib_tparm.c +++ b/ncurses/tinfo/lib_tparm.c @@ -1086,6 +1086,64 @@ tparam_internal(TPARM_STATE *tps, const char *string, TPARM_DATA *data) return (TPS(out_buff)); } +#ifdef CUR +/* + * Only a few standard capabilities accept string parameters. The others that + * are parameterized accept only numeric parameters. + */ +static bool +check_string_caps(TPARM_DATA *data, const char *string) +{ + bool result = FALSE; + +#define CHECK_CAP(name) (VALID_STRING(name) && !strcmp(name, string)) + + /* + * Disallow string parameters unless we can check them against a terminal + * description. + */ + if (cur_term != NULL) { + int want_type = 0; + + if (CHECK_CAP(pkey_key)) + want_type = 2; /* function key #1, type string #2 */ + else if (CHECK_CAP(pkey_local)) + want_type = 2; /* function key #1, execute string #2 */ + else if (CHECK_CAP(pkey_xmit)) + want_type = 2; /* function key #1, transmit string #2 */ + else if (CHECK_CAP(plab_norm)) + want_type = 2; /* label #1, show string #2 */ + else if (CHECK_CAP(pkey_plab)) + want_type = 6; /* function key #1, type string #2, show string #3 */ +#if NCURSES_XNAMES + else { + char *check; + + check = tigetstr("Cs"); + if (CHECK_CAP(check)) + want_type = 1; /* style #1 */ + + check = tigetstr("Ms"); + if (CHECK_CAP(check)) + want_type = 3; /* storage unit #1, content #2 */ + } +#endif + + if (want_type == data->tparm_type) { + result = TRUE; + } else { + T(("unexpected string-parameter")); + } + } + return result; +} + +#define ValidCap() (myData.tparm_type == 0 || \ + check_string_caps(&myData, string)) +#else +#define ValidCap() 1 +#endif + #if NCURSES_TPARM_VARARGS NCURSES_EXPORT(char *) @@ -1100,7 +1158,7 @@ tparm(const char *string, ...) tps->tname = "tparm"; #endif /* TRACE */ - if (tparm_setup(cur_term, string, &myData) == OK) { + if (tparm_setup(cur_term, string, &myData) == OK && ValidCap()) { va_list ap; va_start(ap, string); @@ -1135,7 +1193,7 @@ tparm(const char *string, tps->tname = "tparm"; #endif /* TRACE */ - if (tparm_setup(cur_term, string, &myData) == OK) { + if (tparm_setup(cur_term, string, &myData) == OK && ValidCap()) { myData.param[0] = a1; myData.param[1] = a2; @@ -1166,7 +1224,7 @@ tiparm(const char *string, ...) tps->tname = "tiparm"; #endif /* TRACE */ - if (tparm_setup(cur_term, string, &myData) == OK) { + if (tparm_setup(cur_term, string, &myData) == OK && ValidCap()) { va_list ap; va_start(ap, string); @@ -1179,7 +1237,25 @@ tiparm(const char *string, ...) } /* - * The internal-use flavor ensures that the parameters are numbers, not strings + * The internal-use flavor ensures that parameters are numbers, not strings. + * In addition to ensuring that they are numbers, it ensures that the parameter + * count is consistent with intended usage. + * + * Unlike the general-purpose tparm/tiparm, these internal calls are fairly + * well defined: + * + * expected == 0 - not applicable + * expected == 1 - set color, or vertical/horizontal addressing + * expected == 2 - cursor addressing + * expected == 4 - initialize color or color pair + * expected == 9 - set attributes + * + * Only for the last case (set attributes) should a parameter be optional. + * Also, a capability which calls for more parameters than expected should be + * ignored. + * + * Return a null if the parameter-checks fail. Otherwise, return a pointer to + * the formatted capability string. */ NCURSES_EXPORT(char *) _nc_tiparm(int expected, const char *string, ...) @@ -1189,22 +1265,36 @@ _nc_tiparm(int expected, const char *string, ...) char *result = NULL; _nc_tparm_err = 0; + T((T_CALLED("_nc_tiparm(%d, %s, ...)"), expected, _nc_visbuf(string))); #ifdef TRACE tps->tname = "_nc_tiparm"; #endif /* TRACE */ - if (tparm_setup(cur_term, string, &myData) == OK - && myData.num_actual <= expected - && myData.tparm_type == 0) { - va_list ap; + if (tparm_setup(cur_term, string, &myData) == OK && ValidCap()) { + if (myData.num_actual == 0) { + T(("missing parameter%s, expected %s%d", + expected > 1 ? "s" : "", + expected == 9 ? "up to " : "", + expected)); + } else if (myData.num_actual > expected) { + T(("too many parameters, have %d, expected %d", + myData.num_actual, + expected)); + } else if (expected != 9 && myData.num_actual != expected) { + T(("expected %d parameters, have %d", + myData.num_actual, + expected)); + } else { + va_list ap; - va_start(ap, string); - tparm_copy_valist(&myData, FALSE, ap); - va_end(ap); + va_start(ap, string); + tparm_copy_valist(&myData, FALSE, ap); + va_end(ap); - result = tparam_internal(tps, string, &myData); + result = tparam_internal(tps, string, &myData); + } } - return result; + returnPtr(result); } /* diff --git a/ncurses/tinfo/read_entry.c b/ncurses/tinfo/read_entry.c index 2b1875ed..341337d2 100644 --- a/ncurses/tinfo/read_entry.c +++ b/ncurses/tinfo/read_entry.c @@ -323,6 +323,9 @@ _nc_read_termtype(TERMTYPE2 *ptr, char *buffer, int limit) || bool_count < 0 || num_count < 0 || str_count < 0 + || bool_count > BOOLCOUNT + || num_count > NUMCOUNT + || str_count > STRCOUNT || str_size < 0) { returnDB(TGETENT_NO); } diff --git a/progs/tic.c b/progs/tic.c index 93a0b491..888927e2 100644 --- a/progs/tic.c +++ b/progs/tic.c @@ -2270,9 +2270,15 @@ check_1_infotocap(const char *name, NCURSES_CONST char *value, int count) _nc_reset_tparm(NULL); switch (actual) { + case Str: + result = TPARM_1(value, strings[1]); + break; case Num_Str: result = TPARM_2(value, numbers[1], strings[2]); break; + case Str_Str: + result = TPARM_2(value, strings[1], strings[2]); + break; case Num_Str_Str: result = TPARM_3(value, numbers[1], strings[2], strings[3]); break; diff --git a/progs/tparm_type.c b/progs/tparm_type.c index 3da4a077..644aa62a 100644 --- a/progs/tparm_type.c +++ b/progs/tparm_type.c @@ -47,6 +47,7 @@ tparm_type(const char *name) {code, {longname} }, \ {code, {ti} }, \ {code, {tc} } +#define XD(code, onlyname) TD(code, onlyname, onlyname, onlyname) TParams result = Numbers; /* *INDENT-OFF* */ static const struct { @@ -58,6 +59,10 @@ tparm_type(const char *name) TD(Num_Str, "pkey_xmit", "pfx", "px"), TD(Num_Str, "plab_norm", "pln", "pn"), TD(Num_Str_Str, "pkey_plab", "pfxl", "xl"), +#if NCURSES_XNAMES + XD(Str, "Cs"), + XD(Str_Str, "Ms"), +#endif }; /* *INDENT-ON* */ @@ -80,12 +85,16 @@ guess_tparm_type(int nparam, char **p_is_s) case 1: if (!p_is_s[0]) result = Numbers; + if (p_is_s[0]) + result = Str; break; case 2: if (!p_is_s[0] && !p_is_s[1]) result = Numbers; if (!p_is_s[0] && p_is_s[1]) result = Num_Str; + if (p_is_s[0] && p_is_s[1]) + result = Str_Str; break; case 3: if (!p_is_s[0] && !p_is_s[1] && !p_is_s[2]) diff --git a/progs/tparm_type.h b/progs/tparm_type.h index 7c102a30..af5bcf0f 100644 --- a/progs/tparm_type.h +++ b/progs/tparm_type.h @@ -45,8 +45,10 @@ typedef enum { Other = -1 ,Numbers = 0 + ,Str ,Num_Str ,Num_Str_Str + ,Str_Str } TParams; extern TParams tparm_type(const char *name); diff --git a/progs/tput.c b/progs/tput.c index 4cd0c5ba..41508b72 100644 --- a/progs/tput.c +++ b/progs/tput.c @@ -1,5 +1,5 @@ /**************************************************************************** - * Copyright 2018-2021,2022 Thomas E. Dickey * + * Copyright 2018-2022,2023 Thomas E. Dickey * * Copyright 1998-2016,2017 Free Software Foundation, Inc. * * * * Permission is hereby granted, free of charge, to any person obtaining a * @@ -47,12 +47,15 @@ #include #include -MODULE_ID("$Id: tput.c,v 1.99 2022/02/26 23:19:31 tom Exp $") +MODULE_ID("$Id: tput.c,v 1.102 2023/04/08 16:26:36 tom Exp $") #define PUTS(s) fputs(s, stdout) const char *_nc_progname = "tput"; +static bool opt_v = FALSE; /* quiet, do not show warnings */ +static bool opt_x = FALSE; /* clear scrollback if possible */ + static bool is_init = FALSE; static bool is_reset = FALSE; static bool is_clear = FALSE; @@ -81,6 +84,7 @@ usage(const char *optstring) KEEP(" -S << read commands from standard input") KEEP(" -T TERM use this instead of $TERM") KEEP(" -V print curses-version") + KEEP(" -v verbose, show warnings") KEEP(" -x do not try to clear scrollback") KEEP("") KEEP("Commands:") @@ -148,7 +152,7 @@ exit_code(int token, int value) * Returns nonzero on error. */ static int -tput_cmd(int fd, TTY * settings, bool opt_x, int argc, char **argv, int *used) +tput_cmd(int fd, TTY * settings, int argc, char **argv, int *used) { NCURSES_CONST char *name; char *s; @@ -231,7 +235,9 @@ tput_cmd(int fd, TTY * settings, bool opt_x, int argc, char **argv, int *used) } else if (VALID_STRING(s)) { if (argc > 1) { int k; + int narg; int analyzed; + int provided; int popcount; long numbers[1 + NUM_PARM]; char *strings[1 + NUM_PARM]; @@ -271,14 +277,45 @@ tput_cmd(int fd, TTY * settings, bool opt_x, int argc, char **argv, int *used) popcount = 0; _nc_reset_tparm(NULL); + /* + * Count the number of numeric parameters which are provided. + */ + provided = 0; + for (narg = 1; narg < argc; ++narg) { + char *ending = NULL; + long check = strtol(argv[narg], &ending, 10); + if (check < 0 || ending == argv[narg] || *ending != '\0') + break; + provided = narg; + } switch (paramType) { + case Str: + s = TPARM_1(s, strings[1]); + analyzed = 1; + if (provided == 0 && argc >= 1) + provided++; + break; + case Str_Str: + s = TPARM_2(s, strings[1], strings[2]); + analyzed = 2; + if (provided == 0 && argc >= 1) + provided++; + if (provided == 1 && argc >= 2) + provided++; + break; case Num_Str: s = TPARM_2(s, numbers[1], strings[2]); analyzed = 2; + if (provided == 1 && argc >= 2) + provided++; break; case Num_Str_Str: s = TPARM_3(s, numbers[1], strings[2], strings[3]); analyzed = 3; + if (provided == 1 && argc >= 2) + provided++; + if (provided == 2 && argc >= 3) + provided++; break; case Numbers: analyzed = _nc_tparm_analyze(NULL, s, p_is_s, &popcount); @@ -316,7 +353,13 @@ tput_cmd(int fd, TTY * settings, bool opt_x, int argc, char **argv, int *used) if (analyzed < popcount) { analyzed = popcount; } - *used += analyzed; + if (opt_v && (analyzed != provided)) { + fprintf(stderr, "%s: %s parameters for \"%s\"\n", + _nc_progname, + (analyzed < provided ? "extra" : "missing"), + argv[0]); + } + *used += provided; } /* use putp() in order to perform padding */ @@ -339,7 +382,6 @@ main(int argc, char **argv) int used; TTY old_settings; TTY tty_settings; - bool opt_x = FALSE; /* clear scrollback if possible */ bool is_alias; bool need_tty; @@ -348,7 +390,7 @@ main(int argc, char **argv) term = getenv("TERM"); - while ((c = getopt(argc, argv, is_alias ? "T:Vx" : "ST:Vx")) != -1) { + while ((c = getopt(argc, argv, is_alias ? "T:Vvx" : "ST:Vvx")) != -1) { switch (c) { case 'S': cmdline = FALSE; @@ -361,6 +403,9 @@ main(int argc, char **argv) case 'V': puts(curses_version()); ExitProgram(EXIT_SUCCESS); + case 'v': /* verbose */ + opt_v = TRUE; + break; case 'x': /* do not try to clear scrollback */ opt_x = TRUE; break; @@ -404,7 +449,7 @@ main(int argc, char **argv) usage(NULL); while (argc > 0) { tty_settings = old_settings; - code = tput_cmd(fd, &tty_settings, opt_x, argc, argv, &used); + code = tput_cmd(fd, &tty_settings, argc, argv, &used); if (code != 0) break; argc -= used; @@ -439,7 +484,7 @@ main(int argc, char **argv) while (argnum > 0) { int code; tty_settings = old_settings; - code = tput_cmd(fd, &tty_settings, opt_x, argnum, argnow, &used); + code = tput_cmd(fd, &tty_settings, argnum, argnow, &used); if (code != 0) { if (result == 0) result = ErrSystem(0); /* will return value >4 */ -- 2.40.0