Skip to content

Commit

Permalink
[FAB-15549] Restrict discovery max combinations
Browse files Browse the repository at this point in the history
This change set restricts discovery maximum combinations via
a heiuristic that detects if the number of combinations of
descendants of a policy vertex is too large, and randomly
prunes the descendants according to the policy threshold.

Change-Id: I5bfe1e4b5586cbb647d20b961cb37c39f09779da
Signed-off-by: yacovm <yacovm@il.ibm.com>
(cherry picked from commit f9caa00)
  • Loading branch information
yacovm committed May 30, 2019
1 parent d6f5384 commit 8d353ba
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 35 deletions.
25 changes: 14 additions & 11 deletions common/graph/choose.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ SPDX-License-Identifier: Apache-2.0

package graph

import "math/big"

type orderedSet struct {
elements []interface{}
}
Expand All @@ -20,18 +22,19 @@ type indiceSet struct {

type indiceSets []*indiceSet

func factorial(n int) int {
m := 1
for i := 1; i <= n; i++ {
m *= i
// CombinationsExceed computes the number of combinations
// of choosing K elements from N elements, and returns
// whether the number of combinations exceeds a given threshold.
// If n < k then it returns false.
func CombinationsExceed(n, k, threshold int) bool {
if n < k {
return false
}
return m
}

func nChooseK(n, k int) int {
a := factorial(n)
b := factorial(n-k) * factorial(k)
return a / b
combinations := &big.Int{}
combinations = combinations.Binomial(int64(n), int64(k))
t := &big.Int{}
t.SetInt64(int64(threshold))
return combinations.Cmp(t) > 0
}

func chooseKoutOfN(n, k int) indiceSets {
Expand Down
22 changes: 11 additions & 11 deletions common/graph/choose_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ import (
"github.com/stretchr/testify/assert"
)

func TestChoose(t *testing.T) {
assert.Equal(t, 24, factorial(4))
assert.Equal(t, 1, factorial(0))
assert.Equal(t, 1, factorial(1))
assert.Equal(t, 15504, nChooseK(20, 5))
for n := 1; n < 20; n++ {
for k := 1; k < n; k++ {
g := chooseKoutOfN(n, k)
assert.Equal(t, nChooseK(n, k), len(g), "n=%d, k=%d", n, k)
}
}
func TestCombinationsExceed(t *testing.T) {
// 20 choose 5 is 15504.
assert.False(t, CombinationsExceed(20, 5, 15504))
assert.False(t, CombinationsExceed(20, 5, 15505))
assert.True(t, CombinationsExceed(20, 5, 15503))

// A huge number of combinations doesn't overflow.
assert.True(t, CombinationsExceed(10000, 500, 9000))

// N < K returns false
assert.False(t, CombinationsExceed(20, 30, 0))
}
20 changes: 19 additions & 1 deletion common/graph/perm.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,28 @@ SPDX-License-Identifier: Apache-2.0

package graph

import (
"math/rand"
"time"
)

func init() {
rand.Seed(time.Now().UnixNano())
}

// treePermutations represents possible permutations
// of a tree
type treePermutations struct {
combinationUpperBound int // The upper bound of combinations of direct descendants.
originalRoot *TreeVertex // The root vertex of all sub-trees
permutations []*TreeVertex // The accumulated permutations
descendantPermutations map[*TreeVertex][][]*TreeVertex // Defines the combinations of sub-trees based on the threshold of the current vertex
}

// newTreePermutation creates a new treePermutations object with a given root vertex
func newTreePermutation(root *TreeVertex) *treePermutations {
func newTreePermutation(root *TreeVertex, combinationUpperBound int) *treePermutations {
return &treePermutations{
combinationUpperBound: combinationUpperBound,
descendantPermutations: make(map[*TreeVertex][][]*TreeVertex),
originalRoot: root,
permutations: []*TreeVertex{root},
Expand Down Expand Up @@ -96,6 +107,13 @@ func (tp *treePermutations) computeDescendantPermutations() {
continue
}

// Ensure we don't have too much combinations of descendants
for CombinationsExceed(len(v.Descendants), v.Threshold, tp.combinationUpperBound) {
// Randomly pick a descendant, and remove it
victim := rand.Intn(len(v.Descendants))
v.Descendants = append(v.Descendants[:victim], v.Descendants[victim+1:]...)
}

// Iterate over all options of selecting the threshold out of the descendants
for _, el := range chooseKoutOfN(len(v.Descendants), v.Threshold) {
// And for each such option, append it to the current TreeVertex
Expand Down
15 changes: 13 additions & 2 deletions common/graph/perm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ SPDX-License-Identifier: Apache-2.0
package graph

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
)

func TestF(t *testing.T) {
func TestPermute(t *testing.T) {
vR := NewTreeVertex("r", nil)
vR.Threshold = 2

Expand All @@ -34,7 +35,7 @@ func TestF(t *testing.T) {
vF.AddDescendant(NewTreeVertex(id, nil))
}

permutations := vR.ToTree().Permute()
permutations := vR.ToTree().Permute(1000)
// For a sub-tree with r-(D,E) we have 9 combinations (3 combinations of each sub-tree where D and E are the roots)
// For a sub-tree with r-(D,F) we have 9 combinations from the same logic
// For a sub-tree with r-(E,F) we have 9 combinations too
Expand All @@ -61,3 +62,13 @@ func TestF(t *testing.T) {
expectedScan = []string{"r", "E", "F", "b", "c", "2", "3"}
assert.Equal(t, expectedScan, listCombination(permutations[26].BFS()))
}

func TestPermuteTooManyCombinations(t *testing.T) {
root := NewTreeVertex("r", nil)
root.Threshold = 500
for i := 0; i < 1000; i++ {
root.AddDescendant(NewTreeVertex(fmt.Sprintf("%d", i), nil))
}
permutations := root.ToTree().Permute(501)
assert.Len(t, permutations, 501)
}
9 changes: 7 additions & 2 deletions common/graph/tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,13 @@ type Tree struct {

// Permute returns Trees that their vertices and edges all exist in the original tree.
// The permutations are calculated according to the thresholds of all vertices.
func (t *Tree) Permute() []*Tree {
return newTreePermutation(t.Root).permute()
// The combinationUpperBound is an upper bound of possible combinations of direct descendants
// of a vertex. If the vertex has a threshold and descendants that result a number of combinations
// that exceeds the given combinationUpperBound, the descendants are pruned until the number of
// combinations is lower than the combinationUpperBound.
// This is done in order to cap the memory usage of the computed result.
func (t *Tree) Permute(combinationUpperBound int) []*Tree {
return newTreePermutation(t.Root, combinationUpperBound).permute()
}

// BFS returns an iterator that iterates the vertices
Expand Down
6 changes: 5 additions & 1 deletion common/policies/inquire/inquire.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ import (

var logger = flogging.MustGetLogger("policies/inquire")

const (
combinationsUpperBound = 10000
)

type inquireableSignaturePolicy struct {
sigPol *common.SignaturePolicyEnvelope
}
Expand All @@ -36,7 +40,7 @@ func (isp *inquireableSignaturePolicy) SatisfiedBy() []policies.PrincipalSet {
root := graph.NewTreeVertex(rootId, isp.sigPol.Rule)
computePolicyTree(root)
var res []policies.PrincipalSet
for _, perm := range root.ToTree().Permute() {
for _, perm := range root.ToTree().Permute(combinationsUpperBound) {
principalSet := principalsOfTree(perm, isp.sigPol.Identities)
if len(principalSet) == 0 {
return nil
Expand Down
41 changes: 34 additions & 7 deletions common/policies/inquire/inquire_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,13 @@ var cases = []testCase{
},
}

func TestSatisfiedBy(t *testing.T) {

mspId := func(principal *msp.MSPPrincipal) string {
role := &msp.MSPRole{}
proto.Unmarshal(principal.Principal, role)
return role.MspIdentifier
}
func mspId(principal *msp.MSPPrincipal) string {
role := &msp.MSPRole{}
proto.Unmarshal(principal.Principal, role)
return role.MspIdentifier
}

func TestSatisfiedBy(t *testing.T) {
for _, test := range cases {
t.Run(test.name, func(t *testing.T) {
p, err := cauthdsl.FromString(test.policy)
Expand All @@ -106,3 +105,31 @@ func TestSatisfiedBy(t *testing.T) {
})
}
}

func TestSatisfiedByTooManyCombinations(t *testing.T) {
// We have 26 choose 15 members which is 7,726,160
// and we ensure we don't return so many combinations,
// but limit it to combinationsUpperBound.

p, err := cauthdsl.FromString("OutOf(15, 'A.member', 'B.member', 'C.member', 'D.member', 'E.member', 'F.member'," +
" 'G.member', 'H.member', 'I.member', 'J.member', 'K.member', 'L.member', 'M.member', 'N.member', 'O.member', " +
"'P.member', 'Q.member', 'R.member', 'S.member', 'T.member', 'U.member', 'V.member', 'W.member', 'X.member', " +
"'Y.member', 'Z.member')")
assert.NoError(t, err)

ip := NewInquireableSignaturePolicy(p)
satisfiedBy := ip.SatisfiedBy()

actual := make(map[string]struct{})
for _, ps := range satisfiedBy {
// Every subset is of size 15, as needed by the endorsement policy.
assert.Len(t, ps, 15)
var principals []string
for _, principal := range ps {
principals = append(principals, mspId(principal))
}
actual[fmt.Sprintf("%v", principals)] = struct{}{}
}
// Total combinations are capped by the combinationsUpperBound.
assert.True(t, len(actual) < combinationsUpperBound)
}

0 comments on commit 8d353ba

Please sign in to comment.