From 38dc00a053329a1506488250a5202b44c48dc4cd Mon Sep 17 00:00:00 2001 From: malsomesh9 Date: Tue, 7 Apr 2026 02:19:48 +0530 Subject: [PATCH] fix(policydsl): guard against deeply nested policies Signed-off-by: malsomesh9 --- common/policydsl/policyparser.go | 32 +++++++++++++++++++++++++++ common/policydsl/policyparser_test.go | 20 +++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/common/policydsl/policyparser.go b/common/policydsl/policyparser.go index aa6f514370d..88b808d620c 100644 --- a/common/policydsl/policyparser.go +++ b/common/policydsl/policyparser.go @@ -26,6 +26,11 @@ const ( GateOutOf = "OutOf" ) +const ( + maxPolicyStringLength = 100000 + maxPolicyNestingDepth = 1024 +) + // Role values for principals const ( RoleAdmin = "admin" @@ -233,6 +238,29 @@ func newContext() *context { return &context{IDNum: 0, principals: make([]*mb.MSPPrincipal, 0)} } +func validatePolicyString(policy string) error { + if len(policy) > maxPolicyStringLength { + return fmt.Errorf("policy string exceeds maximum length of %d characters", maxPolicyStringLength) + } + + depth := 0 + for _, r := range policy { + switch r { + case '(': + depth++ + if depth > maxPolicyNestingDepth { + return fmt.Errorf("policy string exceeds maximum nesting depth of %d", maxPolicyNestingDepth) + } + case ')': + if depth > 0 { + depth-- + } + } + } + + return nil +} + // FromString takes a string representation of the policy, // parses it and returns a SignaturePolicyEnvelope that // implements that policy. The supported language is as follows: @@ -252,6 +280,10 @@ func newContext() *context { // - ROLE takes the value of any of the RoleXXX constants representing // the required role func FromString(policy string) (*cb.SignaturePolicyEnvelope, error) { + if err := validatePolicyString(policy); err != nil { + return nil, err + } + // first we translate the and/or business into outof gates intermediate, err := govaluate.NewEvaluableExpressionWithFunctions( policy, map[string]govaluate.ExpressionFunction{ diff --git a/common/policydsl/policyparser_test.go b/common/policydsl/policyparser_test.go index d4da2e9a6c6..c820f6a26e0 100644 --- a/common/policydsl/policyparser_test.go +++ b/common/policydsl/policyparser_test.go @@ -7,6 +7,8 @@ SPDX-License-Identifier: Apache-2.0 package policydsl import ( + "fmt" + "strings" "testing" "github.com/hyperledger/fabric-protos-go-apiv2/common" @@ -406,3 +408,21 @@ func TestSecondPassBoundaryCheck(t *testing.T) { require.Nil(t, p3) require.EqualError(t, err3, "invalid t-out-of-n predicate, t 4, n 2") } + +func TestPolicyStringGuardrails(t *testing.T) { + t.Run("rejects excessive nesting", func(t *testing.T) { + policy := strings.Repeat("OR(", maxPolicyNestingDepth+1) + "'A.member'" + strings.Repeat(")", maxPolicyNestingDepth+1) + + p, err := FromString(policy) + require.Nil(t, p) + require.EqualError(t, err, fmt.Sprintf("policy string exceeds maximum nesting depth of %d", maxPolicyNestingDepth)) + }) + + t.Run("rejects oversized policy strings", func(t *testing.T) { + policy := "OR('A.member', '" + strings.Repeat("B", maxPolicyStringLength) + ".member')" + + p, err := FromString(policy) + require.Nil(t, p) + require.EqualError(t, err, fmt.Sprintf("policy string exceeds maximum length of %d characters", maxPolicyStringLength)) + }) +}