From c3bfdac8e0e9a21d524ad72036953f68d2193e52 Mon Sep 17 00:00:00 2001
From: Natanael Copa <ncopa@alpinelinux.org>
Date: Tue, 21 May 2024 14:46:08 +0200
Subject: [PATCH 2/2] awk: fix ternary operator and precedence of =

Adjust the = precedence test to match behavior of gawk, mawk and
FreeBSD.  awk 'BEGIN {print v=3==3; print v}' should print two '1'.

To fix this, and to unbreak the ternary conditional operator, we restore
the precedence of = in the token list, but override this with a lower
priority when the assignment is on the right side of a compare.

This fixes commit 0256e00a9d07 (awk: fix precedence of = relative to ==) [1]

CVE: CVE-2023-42364 CVE-2023-42365

Upstream-Status: Submitted [http://lists.busybox.net/pipermail/busybox/2024-May/090766.html]

[1] https://bugs.busybox.net/show_bug.cgi?id=15871#c6

Signed-off-by: Natanael Copa <ncopa@alpinelinux.org>
(cherry picked from commit 1714301c405ef03b39605c85c23f22a190cddd95)
Signed-off-by: Khem Raj <raj.khem@gmail.com>
---
 editors/awk.c       | 18 ++++++++++++++----
 testsuite/awk.tests |  9 +++++++--
 2 files changed, 21 insertions(+), 6 deletions(-)

diff --git a/editors/awk.c b/editors/awk.c
index aff86fe..f320d8c 100644
--- a/editors/awk.c
+++ b/editors/awk.c
@@ -442,9 +442,10 @@ static const uint32_t tokeninfo[] ALIGN4 = {
 #define TI_PREINC (OC_UNARY|xV|P(9)|'P')
 #define TI_PREDEC (OC_UNARY|xV|P(9)|'M')
 	TI_PREINC,               TI_PREDEC,               OC_FIELD|xV|P(5),
-	OC_COMPARE|VV|P(39)|5,   OC_MOVE|VV|P(38),        OC_REPLACE|NV|P(38)|'+', OC_REPLACE|NV|P(38)|'-',
-	OC_REPLACE|NV|P(38)|'*', OC_REPLACE|NV|P(38)|'/', OC_REPLACE|NV|P(38)|'%', OC_REPLACE|NV|P(38)|'&',
-	OC_BINARY|NV|P(29)|'+',  OC_BINARY|NV|P(29)|'-',  OC_REPLACE|NV|P(38)|'&', OC_BINARY|NV|P(15)|'&',
+#define TI_ASSIGN (OC_MOVE|VV|P(74))
+	OC_COMPARE|VV|P(39)|5,   TI_ASSIGN,               OC_REPLACE|NV|P(74)|'+', OC_REPLACE|NV|P(74)|'-',
+	OC_REPLACE|NV|P(74)|'*', OC_REPLACE|NV|P(74)|'/', OC_REPLACE|NV|P(74)|'%', OC_REPLACE|NV|P(74)|'&',
+	OC_BINARY|NV|P(29)|'+',  OC_BINARY|NV|P(29)|'-',  OC_REPLACE|NV|P(74)|'&', OC_BINARY|NV|P(15)|'&',
 	OC_BINARY|NV|P(25)|'/',  OC_BINARY|NV|P(25)|'%',  OC_BINARY|NV|P(15)|'&',  OC_BINARY|NV|P(25)|'*',
 	OC_COMPARE|VV|P(39)|4,   OC_COMPARE|VV|P(39)|3,   OC_COMPARE|VV|P(39)|0,   OC_COMPARE|VV|P(39)|1,
 #define TI_LESS     (OC_COMPARE|VV|P(39)|2)
@@ -1376,11 +1377,19 @@ static node *parse_expr(uint32_t term_tc)
 			continue;
 		}
 		if (tc & (TS_BINOP | TC_UOPPOST)) {
+			int prio;
 			debug_printf_parse("%s: TS_BINOP | TC_UOPPOST tc:%x\n", __func__, tc);
 			/* for binary and postfix-unary operators, jump back over
 			 * previous operators with higher priority */
 			vn = cn;
-			while (((t_info & PRIMASK) > (vn->a.n->info & PRIMASK2))
+			/* Let assignment get higher priority when used on right
+			 * side in compare. i.e: 2==v=3 */
+			if (t_info == TI_ASSIGN && (vn->a.n->info & OPCLSMASK) == OC_COMPARE) {
+				prio = PRECEDENCE(38);
+			} else {
+				prio = (t_info & PRIMASK);
+			}
+			while ((prio > (vn->a.n->info & PRIMASK2))
 			    || (t_info == vn->info && t_info == TI_COLON)
 			) {
 				vn = vn->a.n;
@@ -1412,6 +1421,7 @@ static node *parse_expr(uint32_t term_tc)
 					if ((vn->info & OPCLSMASK) != OC_VAR
 					 && (vn->info & OPCLSMASK) != OC_FNARG
 					 && (vn->info & OPCLSMASK) != OC_FIELD
+					 && (vn->info & OPCLSMASK) != OC_COMPARE
 					) {
 						syntax_error(EMSG_UNEXP_TOKEN); /* no. bad */
 					}
diff --git a/testsuite/awk.tests b/testsuite/awk.tests
index a78fdcd..d2706de 100755
--- a/testsuite/awk.tests
+++ b/testsuite/awk.tests
@@ -540,9 +540,14 @@ testing 'awk assign while assign' \
 │    trim/eff : 57.02%/26, 0.00%                     │          [cpu000:100%]
 └────────────────────────────────────────────────────┘^C"
 
-testing "awk = has higher precedence than == (despite what gawk manpage claims)" \
+testing "awk = has higher precedence than == on right side" \
 	"awk 'BEGIN { v=1; print 2==v; print 2==v=2; print v; print v=3==3; print v}'" \
-	'0\n1\n2\n1\n3\n' \
+	'0\n1\n2\n1\n1\n' \
+	'' ''
+
+testing 'awk ternary precedence' \
+	"awk 'BEGIN { a = 0 ? \"yes\": \"no\"; print a }'" \
+	'no\n' \
 	'' ''
 
 exit $FAILCOUNT
