From 22d98e132502cc4d4dba751ac57a00cf71c07184 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 4 Oct 2023 13:56:57 -0400 Subject: [PATCH 1/2] add tests --- .../tests/interpreter/entitlements_test.go | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/runtime/tests/interpreter/entitlements_test.go b/runtime/tests/interpreter/entitlements_test.go index 42e7c57f60..fe32f2f133 100644 --- a/runtime/tests/interpreter/entitlements_test.go +++ b/runtime/tests/interpreter/entitlements_test.go @@ -3280,3 +3280,74 @@ func TestInterpretMappingInclude(t *testing.T) { ) }) } + +func TestInterpretMappingEscalation(t *testing.T) { + + t.Parallel() + + t.Run("escalate", func(t *testing.T) { + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + entitlement X + entitlement Y + entitlement mapping M { + X -> Insert + Y -> Remove + } + struct S { + access(M) var member: auth(M) &[Int]? + init() { + self.member = nil; + } + access(all) fun grantRemovePrivileges(param: auth(Insert) &[Int]): Void{ + var selfRef = &self as auth(X) &S; + selfRef.member = param; + } + } + fun main(): Void { + var arr: [Int] = [123]; + var arrRef = &arr as auth(Insert) &[Int]; + let s = S() + s.grantRemovePrivileges(param: arrRef); + s.member?.removeLast() // Caught by checkMemberAccess type check + } + `) + + _, err := inter.Invoke("main") + assert.NoError(t, err) + }) + + t.Run("field assign", func(t *testing.T) { + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + entitlement X + entitlement Y + entitlement mapping M { + X -> Insert + Y -> Remove + } + struct S { + access(M) var member: auth(M) &[Int]? + init() { + self.member = nil; + } + access(all) fun grantRemovePrivileges(sRef: auth(X) &S, param: auth(Insert) &[Int]): Void{ + sRef.member = param; + } + } + fun main(): Void { + var arr: [Int] = [123]; + var arrRef = &arr as auth(Insert) &[Int]; + let s = S() + s.grantRemovePrivileges(sRef: &s as auth(X) &S, param: arrRef); + s.member?.removeLast() // Caught by checkMemberAccess type check + } + `) + + _, err := inter.Invoke("main") + assert.NoError(t, err) + }) + +} From 787fdbe4b4364ee7c4425cedf0dfa4616cab2b24 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 4 Oct 2023 15:06:53 -0400 Subject: [PATCH 2/2] require full entitlements to assign to a mapped field --- runtime/sema/check_member_expression.go | 22 ++++++ runtime/tests/checker/entitlements_test.go | 71 +++++++++++++++++++ .../tests/interpreter/entitlements_test.go | 71 ------------------- 3 files changed, 93 insertions(+), 71 deletions(-) diff --git a/runtime/sema/check_member_expression.go b/runtime/sema/check_member_expression.go index 0ff70a37f5..4d1d044a0d 100644 --- a/runtime/sema/check_member_expression.go +++ b/runtime/sema/check_member_expression.go @@ -455,6 +455,28 @@ func (checker *Checker) mapAccess( // pretend that the access succeeds to prevent a redundant access error report return true, UnauthorizedAccess } + // when we are in an assignment statement, + // we need full permissions to the map regardless of the input authorization of the reference + // Consider: + // + // entitlement X + // entitlement Y + // entitlement mapping M { + // X -> Insert + // Y -> Remove + // } + // struct S { + // access(M) var member: auth(M) &[T]? + // ... + // } + // + // If we were able to assign a `auth(Insert) &[T]` value to `ref.member` when `ref` has type `auth(X) &S` + // we could use this to then extract a `auth(Insert, Remove) &[T]` reference to that array by accessing `member` + // on an owned copy of `S`. As such, when in an assignment, we return the full codomain here as the "granted authorization" + // of the access expression, since the checker will later enforce that the incoming reference value is a subtype of that full codomain. + if checker.inAssignment { + return true, mappedAccess.Codomain() + } return true, grantedAccess case *OptionalType: diff --git a/runtime/tests/checker/entitlements_test.go b/runtime/tests/checker/entitlements_test.go index 3a997c54d5..727c0f613d 100644 --- a/runtime/tests/checker/entitlements_test.go +++ b/runtime/tests/checker/entitlements_test.go @@ -7369,3 +7369,74 @@ func TestCheckEntitlementMissingInMap(t *testing.T) { require.IsType(t, &sema.InvalidNonEntitlementTypeInMapError{}, errors[0]) }) } + +func TestInterpretMappingEscalation(t *testing.T) { + + t.Parallel() + + t.Run("escalate", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + entitlement X + entitlement Y + entitlement mapping M { + X -> Insert + Y -> Remove + } + struct S { + access(M) var member: auth(M) &[Int]? + init() { + self.member = nil; + } + access(all) fun grantRemovePrivileges(param: auth(Insert) &[Int]): Void{ + var selfRef = &self as auth(X) &S; + selfRef.member = param; + } + } + fun main(): Void { + var arr: [Int] = [123]; + var arrRef = &arr as auth(Insert) &[Int]; + let s = S() + s.grantRemovePrivileges(param: arrRef); + s.member?.removeLast() + } + `) + + errors := RequireCheckerErrors(t, err, 1) + require.IsType(t, &sema.TypeMismatchError{}, errors[0]) + }) + + t.Run("field assign", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + entitlement X + entitlement Y + entitlement mapping M { + X -> Insert + Y -> Remove + } + struct S { + access(M) var member: auth(M) &[Int]? + init() { + self.member = nil; + } + access(all) fun grantRemovePrivileges(sRef: auth(X) &S, param: auth(Insert) &[Int]): Void{ + sRef.member = param; + } + } + fun main(): Void { + var arr: [Int] = [123]; + var arrRef = &arr as auth(Insert) &[Int]; + let s = S() + s.grantRemovePrivileges(sRef: &s as auth(X) &S, param: arrRef); + s.member?.removeLast() + } + `) + + errors := RequireCheckerErrors(t, err, 1) + require.IsType(t, &sema.TypeMismatchError{}, errors[0]) + }) + +} diff --git a/runtime/tests/interpreter/entitlements_test.go b/runtime/tests/interpreter/entitlements_test.go index fe32f2f133..42e7c57f60 100644 --- a/runtime/tests/interpreter/entitlements_test.go +++ b/runtime/tests/interpreter/entitlements_test.go @@ -3280,74 +3280,3 @@ func TestInterpretMappingInclude(t *testing.T) { ) }) } - -func TestInterpretMappingEscalation(t *testing.T) { - - t.Parallel() - - t.Run("escalate", func(t *testing.T) { - t.Parallel() - - inter := parseCheckAndInterpret(t, ` - entitlement X - entitlement Y - entitlement mapping M { - X -> Insert - Y -> Remove - } - struct S { - access(M) var member: auth(M) &[Int]? - init() { - self.member = nil; - } - access(all) fun grantRemovePrivileges(param: auth(Insert) &[Int]): Void{ - var selfRef = &self as auth(X) &S; - selfRef.member = param; - } - } - fun main(): Void { - var arr: [Int] = [123]; - var arrRef = &arr as auth(Insert) &[Int]; - let s = S() - s.grantRemovePrivileges(param: arrRef); - s.member?.removeLast() // Caught by checkMemberAccess type check - } - `) - - _, err := inter.Invoke("main") - assert.NoError(t, err) - }) - - t.Run("field assign", func(t *testing.T) { - t.Parallel() - - inter := parseCheckAndInterpret(t, ` - entitlement X - entitlement Y - entitlement mapping M { - X -> Insert - Y -> Remove - } - struct S { - access(M) var member: auth(M) &[Int]? - init() { - self.member = nil; - } - access(all) fun grantRemovePrivileges(sRef: auth(X) &S, param: auth(Insert) &[Int]): Void{ - sRef.member = param; - } - } - fun main(): Void { - var arr: [Int] = [123]; - var arrRef = &arr as auth(Insert) &[Int]; - let s = S() - s.grantRemovePrivileges(sRef: &s as auth(X) &S, param: arrRef); - s.member?.removeLast() // Caught by checkMemberAccess type check - } - `) - - _, err := inter.Invoke("main") - assert.NoError(t, err) - }) - -}