• R/O
  • SSH
  • HTTPS

yash: 提交


Commit MetaInfo

修訂4197 (tree)
時間2022-08-18 00:10:33
作者magicant

Log Message

Avoid undefined behavior in arithmetic expansion (#45200)

Change Summary

差異

--- yash/trunk/NEWS (revision 4196)
+++ yash/trunk/NEWS (revision 4197)
@@ -10,6 +10,8 @@
1010 ----------------------------------------------------------------------
1111 Yash 2.53 (????-??-??)
1212
13+ = The shell now deterministically rejects arithmetic expansions
14+ that result in undefined behavior.
1315 * A non-interactive yash now exits on an assignment error in a for
1416 loop.
1517 * Fixed a bug where command substitutions contained in the regular
--- yash/trunk/README (revision 4196)
+++ yash/trunk/README (revision 4197)
@@ -91,9 +91,6 @@
9191 * In C, a null character represents the end of a string. If input to
9292 the shell itself contains a null character, characters following
9393 the null character will be ignored.
94- * We assume that an overflow in signed integer arithmetic or type
95- conversion silently yields an implementation-defined integer value
96- rather than resulting in an error.
9794 * The GCC extension keyword `__attribute__' is used in the source
9895 code. When not compiled with GCC, this keyword is removed by the
9996 preprocessor, so generally there is no harm. But if your compiler
--- yash/trunk/arith.c (revision 4196)
+++ yash/trunk/arith.c (revision 4197)
@@ -1,6 +1,6 @@
11 /* Yash: yet another shell */
22 /* arith.c: arithmetic expansion */
3-/* (C) 2007-2019 magicant */
3+/* (C) 2007-2022 magicant */
44
55 /* This program is free software: you can redistribute it and/or modify
66 * it under the terms of the GNU General Public License as published by
@@ -99,9 +99,17 @@
9999 evalinfo_T *info, atokentype_T ttype,
100100 value_T *lhs, value_T *rhs, value_T *result)
101101 __attribute__((nonnull));
102-static long do_long_calculation1(atokentype_T ttype, long v1, long v2);
103-static long do_long_calculation2(atokentype_T ttype, long v1, long v2);
104-static double do_double_calculation(atokentype_T ttype, double v1, double v2);
102+static bool do_long_calculation1(
103+ atokentype_T ttype, long v1, long v2, long *result)
104+ __attribute__((nonnull,warn_unused_result));
105+static bool do_long_calculation2(
106+ atokentype_T ttype, long v1, long v2, long *result)
107+ __attribute__((nonnull,warn_unused_result));
108+static long do_long_comparison(atokentype_T ttype, long v1, long v2)
109+ __attribute__((const,warn_unused_result));
110+static bool do_double_calculation(
111+ atokentype_T ttype, double v1, double v2, double *result)
112+ __attribute__((nonnull,warn_unused_result));
105113 static long do_double_comparison(atokentype_T ttype, double v1, double v2);
106114 static void parse_conditional(evalinfo_T *info, value_T *result)
107115 __attribute__((nonnull));
@@ -129,8 +137,8 @@
129137 __attribute__((nonnull));
130138 static void parse_postfix(evalinfo_T *info, value_T *result)
131139 __attribute__((nonnull));
132-static void do_increment_or_decrement(atokentype_T ttype, value_T *value)
133- __attribute__((nonnull));
140+static bool do_increment_or_decrement(atokentype_T ttype, value_T *value)
141+ __attribute__((nonnull,warn_unused_result));
134142 static void parse_primary(evalinfo_T *info, value_T *result)
135143 __attribute__((nonnull));
136144 static void parse_as_number(evalinfo_T *info, value_T *result)
@@ -144,9 +152,8 @@
144152 __attribute__((nonnull));
145153 static void next_token(evalinfo_T *info)
146154 __attribute__((nonnull));
147-static bool fail_if_will_divide_by_zero(
148- atokentype_T op, const value_T *rhs, evalinfo_T *info, value_T *result)
149- __attribute__((nonnull));
155+static bool long_mul_will_overflow(long v1, long v2)
156+ __attribute__((const,warn_unused_result));
150157
151158
152159 /* Evaluates the specified string as an arithmetic expression.
@@ -329,16 +336,22 @@
329336 case TT_PLUS: case TT_PLUSEQUAL:
330337 case TT_MINUS: case TT_MINUSEQUAL:
331338 result->type = coerce_type(info, lhs, rhs);
332- if (fail_if_will_divide_by_zero(ttype, rhs, info, result))
333- return false;
334339 switch (result->type) {
335340 case VT_LONG:
336- result->v_long = do_long_calculation1(
337- ttype, lhs->v_long, rhs->v_long);
341+ if (!do_long_calculation1(ttype, lhs->v_long, rhs->v_long,
342+ &result->v_long)) {
343+ info->error = true;
344+ result->type = VT_INVALID;
345+ return false;
346+ }
338347 break;
339348 case VT_DOUBLE:
340- result->v_double = do_double_calculation(
341- ttype, lhs->v_double, rhs->v_double);
349+ if (!do_double_calculation(ttype, lhs->v_double,
350+ rhs->v_double, &result->v_double)) {
351+ info->error = true;
352+ result->type = VT_INVALID;
353+ return false;
354+ }
342355 break;
343356 case VT_VAR:
344357 assert(false);
@@ -354,11 +367,17 @@
354367 coerce_integer(info, lhs);
355368 coerce_integer(info, rhs);
356369 if (lhs->type == VT_LONG && rhs->type == VT_LONG) {
357- result->type = VT_LONG;
358- result->v_long =
359- do_long_calculation2(ttype, lhs->v_long, rhs->v_long);
370+ if (do_long_calculation2(
371+ ttype, lhs->v_long, rhs->v_long, &result->v_long)) {
372+ result->type = VT_LONG;
373+ } else {
374+ info->error = true;
375+ result->type = VT_INVALID;
376+ return false;
377+ }
360378 } else {
361379 result->type = VT_INVALID;
380+ return false;
362381 }
363382 break;
364383 case TT_EQUAL:
@@ -370,35 +389,102 @@
370389 return true;
371390 }
372391
373-/* Does unary or binary long calculation according to the specified operator
374- * token. Division by zero is not allowed. */
375-long do_long_calculation1(atokentype_T ttype, long v1, long v2)
392+/* Applies binary operator `ttype' to the given operands `v1' and `v2'.
393+ * If successful, assigns the result to `*result' and returns true.
394+ * Otherwise, prints an error message and returns false. */
395+bool do_long_calculation1(atokentype_T ttype, long v1, long v2, long *result)
376396 {
377397 switch (ttype) {
378398 case TT_PLUS: case TT_PLUSEQUAL:
379- return v1 + v2;
399+ if (v2 >= 0 ? LONG_MAX - v2 < v1 : v1 < LONG_MIN - v2)
400+ goto overflow;
401+ *result = v1 + v2;
402+ return true;
380403 case TT_MINUS: case TT_MINUSEQUAL:
381- return v1 - v2;
404+ if (v2 < 0 ? LONG_MAX + v2 < v1 : v1 < LONG_MIN + v2)
405+ goto overflow;
406+ *result = v1 - v2;
407+ return true;
382408 case TT_ASTER: case TT_ASTEREQUAL:
383- return v1 * v2;
409+ if (long_mul_will_overflow(v1, v2))
410+ goto overflow;
411+ *result = v1 * v2;
412+ return true;
384413 case TT_SLASH: case TT_SLASHEQUAL:
385- return v1 / v2;
414+ if (v2 == 0)
415+ goto division_by_zero;
416+ if (v1 == LONG_MIN && v2 == -1)
417+ goto overflow;
418+ *result = v1 / v2;
419+ return true;
386420 case TT_PERCENT: case TT_PERCENTEQUAL:
387- return v1 % v2;
421+ if (v2 == 0)
422+ goto division_by_zero;
423+ if (v1 == LONG_MIN && v2 == -1)
424+ goto overflow;
425+ *result = v1 % v2;
426+ return true;
388427 default:
389428 assert(false);
390429 }
430+
431+overflow:
432+ xerror(0, Ngt("arithmetic: overflow"));
433+ return false;
434+division_by_zero:
435+ xerror(0, Ngt("arithmetic: division by zero"));
436+ return false;
391437 }
392438
393-/* Does unary or binary long calculation according to the specified operator
394- * token. */
395-long do_long_calculation2(atokentype_T ttype, long v1, long v2)
439+/* Applies binary operator `ttype' to the given operands `v1' and `v2'.
440+ * If successful, assigns the result to `*result' and returns true.
441+ * Otherwise, prints an error message and returns false. */
442+bool do_long_calculation2(atokentype_T ttype, long v1, long v2, long *result)
396443 {
397444 switch (ttype) {
398445 case TT_LESSLESS: case TT_LESSLESSEQUAL:
399- return v1 << v2;
446+ if (v1 < 0)
447+ goto negative_left_shift;
448+ if (v2 < 0 || v2 >= LONG_BIT)
449+ goto invalid_shift_width;
450+ unsigned long u1 = (unsigned long) v1;
451+ if ((u1 << v2 & (unsigned long) LONG_MAX) >> v2 != u1)
452+ goto overflow;
453+ *result = v1 << v2;
454+ return true;
400455 case TT_GREATERGREATER: case TT_GREATERGREATEREQUAL:
401- return v1 >> v2;
456+ if (v2 < 0 || v2 >= LONG_BIT)
457+ goto invalid_shift_width;
458+ *result = v1 >> v2;
459+ return true;
460+ case TT_AMP: case TT_AMPEQUAL:
461+ *result = v1 & v2;
462+ return true;
463+ case TT_HAT: case TT_HATEQUAL:
464+ *result = v1 ^ v2;
465+ return true;
466+ case TT_PIPE: case TT_PIPEEQUAL:
467+ *result = v1 | v2;
468+ return true;
469+ default:
470+ assert(false);
471+ }
472+
473+overflow:
474+ xerror(0, Ngt("arithmetic: overflow"));
475+ return false;
476+negative_left_shift:
477+ xerror(0, Ngt("arithmetic: negative value cannot be shifted to left"));
478+ return false;
479+invalid_shift_width:
480+ xerror(0, Ngt("arithmetic: invalid shift width"));
481+ return false;
482+}
483+
484+/* Applies binary operator `ttype' to the given operands `v1' and `v2'. */
485+long do_long_comparison(atokentype_T ttype, long v1, long v2)
486+{
487+ switch (ttype) {
402488 case TT_LESS:
403489 return v1 < v2;
404490 case TT_LESSEQUAL:
@@ -411,35 +497,50 @@
411497 return v1 == v2;
412498 case TT_EXCLEQUAL:
413499 return v1 != v2;
414- case TT_AMP: case TT_AMPEQUAL:
415- return v1 & v2;
416- case TT_HAT: case TT_HATEQUAL:
417- return v1 ^ v2;
418- case TT_PIPE: case TT_PIPEEQUAL:
419- return v1 | v2;
420500 default:
421501 assert(false);
422502 }
423503 }
424504
425-/* Does unary or binary double calculation according to the specified operator
426- * token. */
427-double do_double_calculation(atokentype_T ttype, double v1, double v2)
505+/* Applies binary operator `ttype' to the given operands `v1' and `v2'.
506+ * If successful, assigns the result to `*result' and returns true.
507+ * Otherwise, prints an error message and returns false. */
508+bool do_double_calculation(
509+ atokentype_T ttype, double v1, double v2, double *result)
428510 {
429511 switch (ttype) {
430512 case TT_PLUS: case TT_PLUSEQUAL:
431- return v1 + v2;
513+ *result = v1 + v2;
514+ return true;
432515 case TT_MINUS: case TT_MINUSEQUAL:
433- return v1 - v2;
516+ *result = v1 - v2;
517+ return true;
434518 case TT_ASTER: case TT_ASTEREQUAL:
435- return v1 * v2;
519+ *result = v1 * v2;
520+ return true;
436521 case TT_SLASH: case TT_SLASHEQUAL:
437- return v1 / v2;
522+#if DOUBLE_DIVISION_BY_ZERO_ERROR
523+ if (v2 == 0.0)
524+ goto division_by_zero;
525+#endif
526+ *result = v1 / v2;
527+ return true;
438528 case TT_PERCENT: case TT_PERCENTEQUAL:
439- return fmod(v1, v2);
529+#if DOUBLE_DIVISION_BY_ZERO_ERROR
530+ if (v2 == 0.0)
531+ goto division_by_zero;
532+#endif
533+ *result = fmod(v1, v2);
534+ return true;
440535 default:
441536 assert(false);
442537 }
538+
539+#if DOUBLE_DIVISION_BY_ZERO_ERROR
540+division_by_zero:
541+ xerror(0, Ngt("arithmetic: division by zero"));
542+ return false;
543+#endif
443544 }
444545
445546 /* Does double comparison according to the specified operator token. */
@@ -660,7 +761,7 @@
660761 parse_relational(info, &rhs);
661762 switch (coerce_type(info, result, &rhs)) {
662763 case VT_LONG:
663- result->v_long = do_long_calculation2(ttype,
764+ result->v_long = do_long_comparison(ttype,
664765 result->v_long, rhs.v_long);
665766 break;
666767 case VT_DOUBLE:
@@ -702,7 +803,7 @@
702803 parse_shift(info, &rhs);
703804 switch (coerce_type(info, result, &rhs)) {
704805 case VT_LONG:
705- result->v_long = do_long_calculation2(ttype,
806+ result->v_long = do_long_comparison(ttype,
706807 result->v_long, rhs.v_long);
707808 break;
708809 case VT_DOUBLE:
@@ -814,8 +915,8 @@
814915 } else if (result->type == VT_VAR) {
815916 word_T saveword = result->v_var;
816917 coerce_number(info, result);
817- do_increment_or_decrement(ttype, result);
818- if (!do_assignment(&saveword, result))
918+ if (!do_increment_or_decrement(ttype, result) ||
919+ !do_assignment(&saveword, result))
819920 info->error = true, result->type = VT_INVALID;
820921 } else if (result->type != VT_INVALID) {
821922 /* TRANSLATORS: This error message is shown when the operand of
@@ -833,7 +934,17 @@
833934 coerce_number(info, result);
834935 if (ttype == TT_MINUS) {
835936 switch (result->type) {
836- case VT_LONG: result->v_long = -result->v_long; break;
937+ case VT_LONG:
938+#if LONG_MIN < -LONG_MAX
939+ if (result->v_long == LONG_MIN) {
940+ xerror(0, Ngt("arithmetic: overflow"));
941+ info->error = true;
942+ result->type = VT_INVALID;
943+ break;
944+ }
945+#endif
946+ result->v_long = -result->v_long;
947+ break;
837948 case VT_DOUBLE: result->v_double = -result->v_double; break;
838949 case VT_INVALID: break;
839950 default: assert(false);
@@ -891,8 +1002,8 @@
8911002 word_T saveword = result->v_var;
8921003 coerce_number(info, result);
8931004 value_T value = *result;
894- do_increment_or_decrement(info->atoken.type, &value);
895- if (!do_assignment(&saveword, &value)) {
1005+ if (!do_increment_or_decrement(info->atoken.type, &value) ||
1006+ !do_assignment(&saveword, &value)) {
8961007 info->error = true;
8971008 result->type = VT_INVALID;
8981009 }
@@ -913,12 +1024,19 @@
9131024
9141025 /* Increment or decrement the specified value.
9151026 * `ttype' must be either TT_PLUSPLUS or TT_MINUSMINUS and the `value' must be
916- * `coerce_number'ed. */
917-void do_increment_or_decrement(atokentype_T ttype, value_T *value)
1027+ * `coerce_number'ed.
1028+ * Returns false on error. */
1029+bool do_increment_or_decrement(atokentype_T ttype, value_T *value)
9181030 {
9191031 if (ttype == TT_PLUSPLUS) {
9201032 switch (value->type) {
921- case VT_LONG: value->v_long++; break;
1033+ case VT_LONG:
1034+ if (value->v_long == LONG_MAX) {
1035+ xerror(0, Ngt("arithmetic: overflow"));
1036+ return false;
1037+ }
1038+ value->v_long++;
1039+ break;
9221040 case VT_DOUBLE: value->v_double++; break;
9231041 case VT_INVALID: break;
9241042 default: assert(false);
@@ -925,12 +1043,19 @@
9251043 }
9261044 } else {
9271045 switch (value->type) {
928- case VT_LONG: value->v_long--; break;
1046+ case VT_LONG:
1047+ if (value->v_long == LONG_MIN) {
1048+ xerror(0, Ngt("arithmetic: overflow"));
1049+ return false;
1050+ }
1051+ value->v_long--;
1052+ break;
9291053 case VT_DOUBLE: value->v_double--; break;
9301054 case VT_INVALID: break;
9311055 default: assert(false);
9321056 }
9331057 }
1058+ return true;
9341059 }
9351060
9361061 /* Parses a primary expression.
@@ -1326,44 +1451,23 @@
13261451 }
13271452 }
13281453
1329-/* If `op' is a division operator and `rhs' is zero, then prints an error
1330- * message, sets `info->error' to true, sets `result->type' to VT_INVALID, and
1331- * returns true. Otherwise, just returns false.
1332- * `rhs->type' must not be VT_VAR. */
1333-bool fail_if_will_divide_by_zero(
1334- atokentype_T op, const value_T *rhs, evalinfo_T *info, value_T *result)
1454+/* Tests whether the multiplication of the given two long values will overflow.
1455+ */
1456+bool long_mul_will_overflow(long v1, long v2)
13351457 {
1336- switch (op) {
1337- case TT_SLASH:
1338- case TT_SLASHEQUAL:
1339- case TT_PERCENT:
1340- case TT_PERCENTEQUAL:
1341- switch (rhs->type) {
1342- case VT_LONG:
1343- if (rhs->v_long == 0)
1344- goto fail;
1345- break;
1346- case VT_DOUBLE:
1347-#if DOUBLE_DIVISION_BY_ZERO_ERROR
1348- if (rhs->v_double == 0.0)
1349- goto fail;
1458+ if (v1 == 0 || v1 == 1 || v2 == 0 || v2 == 1)
1459+ return false;
1460+#if LONG_MIN < -LONG_MAX
1461+ if (v1 == LONG_MIN || v2 == LONG_MIN)
1462+ return true;
13501463 #endif
1351- break;
1352- case VT_VAR:
1353- assert(false);
1354- case VT_INVALID:
1355- break;
1356- }
1357- /* falls through */
1358- default:
1359- return false;
1360- }
1361-
1362-fail:
1363- xerror(0, Ngt("arithmetic: division by zero"));
1364- info->error = true;
1365- result->type = VT_INVALID;
1366- return true;
1464+ unsigned long u1 = labs(v1), u2 = labs(v2);
1465+ unsigned long prod = u1 * u2;
1466+#if LONG_MIN < -LONG_MAX
1467+ if (prod == (unsigned long) LONG_MIN)
1468+ return ((v1 >= 0) == (v2 >= 0)) || prod / u2 != u1;
1469+#endif
1470+ return (prod & (unsigned long) LONG_MAX) / u2 != u1;
13671471 }
13681472
13691473 /* vim: set ts=8 sts=4 sw=4 noet tw=80: */
--- yash/trunk/doc/expand.txt (revision 4196)
+++ yash/trunk/doc/expand.txt (revision 4197)
@@ -466,6 +466,8 @@
466466 echo $((foo + 0)) # error
467467 ----
468468
469+It is an expansion error if the result of an expression is not defined in C.
470+
469471 [[brace]]
470472 == Brace expansion
471473
--- yash/trunk/doc/ja/expand.txt (revision 4196)
+++ yash/trunk/doc/ja/expand.txt (revision 4197)
@@ -276,6 +276,8 @@
276276 echo $((foo + 0)) # エラー
277277 ----
278278
279+C 言語で結果が定義されていない数式は展開エラーになります。
280+
279281 [[brace]]
280282 == ブレース展開
281283
Show on old repository browser