Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added prefix for DARC storage #262

Merged
merged 15 commits into from
Aug 14, 2023
Merged

Added prefix for DARC storage #262

merged 15 commits into from
Aug 14, 2023

Conversation

pierluca
Copy link
Contributor

@pierluca pierluca commented Aug 4, 2023

There's a weakness in Dela (#258) whereby the (existing) smart contracts store key=values without any prefix for the keys in a shared key/value store. Effectively this would allow any malicious user to overwrite other smart contracts' data.

This fixes it for the Distributed Access Rights Controls (DARC) functionality.

@pierluca
Copy link
Contributor Author

pierluca commented Aug 4, 2023

Random thought @jbsv (and @nkcr if you're still peeking 😜):

Right now, we're fixing #258 by adding yet another custom prefix for each contract.
As I'm doing this, I realize that we already have a unique identifying value for each contract, we just happen to use it exclusively for access control.
Should we drop the "custom prefix" (i.e., "VALU", "DARC", etc.), limit the access key (aKey) variable to 4 bytes (down from 32 bytes) and then use it as a prefix in the storage ?
This would avoid the proliferation of constants that should be known for each contract.
Arguably, it would even make sense to make the "aKey" public for each contract, since it'd then also be used as a prefix in the K/V store.

Any thoughts ?

Base automatically changed from value_contract_prefix to extend_crypto August 7, 2023 09:19
Base automatically changed from extend_crypto to master August 7, 2023 10:49
@lanterno
Copy link

lanterno commented Aug 7, 2023

Random thought @jbsv (and @nkcr if you're still peeking 😜):

Right now, we're fixing #258 by adding yet another custom prefix for each contract. As I'm doing this, I realize that we already have a unique identifying value for each contract, we just happen to use it exclusively for access control. Should we drop the "custom prefix" (i.e., "VALU", "DARC", etc.), limit the access key (aKey) variable to 4 bytes (down from 32 bytes) and then use it as a prefix in the storage ? This would avoid the proliferation of constants that should be known for each contract. Arguably, it would even make sense to make the "aKey" public for each contract, since it'd then also be used as a prefix in the K/V store.

Any thoughts ?

I was thinking about that while reading #260 and wondering if there could be a generic way to add the prefix.
but then the prefixKey would need to be centralized, and have access to more than just the key.
so, probably there is 3 problems to solve here

  1. Always adding a prefix while storing a key, and always removing it while reading it.
  2. Centralize the prefixing for all modules that would be interfacing the store - would it extend the existing store module?
  3. change prefixKey to have access to the contextual environment of the key (maybe contract metadata of some sort?)

(I'm still starting to learn more about dela, so excuse me if my lack of knowledge makes this comment not really useful.)

- Contracts have a hardcoded (4-bytes) access key
- Access keys are also used as storage prefix
@pierluca pierluca marked this pull request as draft August 7, 2023 13:14
@pierluca
Copy link
Contributor Author

pierluca commented Aug 7, 2023

You're absolutely right @lanterno. We avoided solution n° 2 as we want the K/V store to be as unaware of its usage as possible. We could add an adapter that implements this prefixing abstraction, but for now we went with just solving n° 1, which is pretty simple and self-sufficient.
A prefixing adapter in front of the storage would probably be cleaner though.

@coveralls
Copy link

coveralls commented Aug 7, 2023

Pull Request Test Coverage Report for Build 5832016077

  • 66 of 69 (95.65%) changed or added relevant lines in 11 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall first build on access_prefix at 98.721%

Changes Missing Coverage Covered Lines Changed/Added Lines %
core/ordering/cosipbft/contracts/viewchange/viewchange.go 12 15 80.0%
Totals Coverage Status
Change from base Build 5794484000: 98.7%
Covered Lines: 14664
Relevant Lines: 14854

💛 - Coveralls

Copy link
Contributor

@jbsv jbsv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apart from some minor changes, LGTM

test/integration_test.go Outdated Show resolved Hide resolved
test/integration_test.go Outdated Show resolved Hide resolved
@jbsv jbsv marked this pull request as ready for review August 11, 2023 10:28
@sonarcloud
Copy link

sonarcloud bot commented Aug 14, 2023

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 1 Code Smell

62.9% 62.9% Coverage
2.2% 2.2% Duplication

@jbsv jbsv merged commit 4bcfa79 into master Aug 14, 2023
7 checks passed
@jbsv jbsv deleted the access_prefix branch August 14, 2023 16:25
@nkcr
Copy link
Contributor

nkcr commented Aug 15, 2023

Scoping the store accesses with a contract prefix is a reasonable solution.

Ultimately, it would be nice to have an access control by sotre elements (i.e. keys) instead of by contracts. It would give more flexibility such as the ability to share keys between contracts.
We are not really far from that actually. This would require to have a custom darc implementation that matches key prefix to R/W/D actions or whatever rules we want to express.

Also, the check could be done at the module level (the "native" smart contract) instead of individually from each contract definition.

Something along those lines:

// execution/native/native.go

// Execute implements execution.Service. It uses the executor to process the
// incoming transaction and return the result.
func (ns *Service) Execute(snap store.Snapshot, step execution.Step) (execution.Result, error) {
	name := string(step.Current.GetArg(ContractArg))

	contract := ns.contracts[name]
	if contract == nil {
		return execution.Result{}, xerrors.Errorf("unknown contract '%s'", name)
	}

	res := execution.Result{
		Accepted: true,
	}

	// should come from the service
	var accessSrv access.Service

	// smart contracts are provided a secured snap that checks actions on it
	secureSnap := newSecureSnap(snap, accessSrv, step.Current.GetIdentity())

	err := contract.Execute(secureSnap, step)
	if err != nil {
		res.Accepted = false
		res.Message = err.Error()
	}

	return res, nil
}

func newSecureSnap(snap store.Snapshot, accessSrv access.Service,
	identity access.Identity) store.Snapshot {

	return secureSnap{...}
}

type secureSnap struct {
	snap      store.Snapshot
	accessSrv access.Service
	identity  access.Identity
}

func (snap secureSnap) Get(key []byte) ([]byte, error) {
	creds := newSnapCred(key, "read")

	err := snap.accessSrv.Match(snap.snap, creds, snap.identity)
	if err != nil {
		return nil, xerrors.Errorf("verification failed: %v", err)
	}

	return snap.snap.Get(key)
}

func (snap secureSnap) Set(key []byte, value []byte) error {
	creds := newSnapCred(key, "write")

	err := snap.accessSrv.Match(snap.snap, creds, snap.identity)
	if err != nil {
		return xerrors.Errorf("verification failed: %v", err)
	}

	return snap.snap.Set(key, value)
}

func (snap secureSnap) Delete(key []byte) error {
	creds := newSnapCred(key, "delete")

	err := snap.accessSrv.Match(snap.snap, creds, snap.identity)
	if err != nil {
		return xerrors.Errorf("verification failed: %v", err)
	}

	return snap.snap.Delete(key)
}

func newSnapCred(key []byte, action string) snapCred {
	return snapCred{
		key:    key,
		action: action,
	}
}

// snapCred defines credentials for operations on a snap. The credentials match
// the "read", "write", "delete" actions defined on the 4 first bytes of a key.
// For example here the rule on a single key prefix:
//
//   -- 0xabcdef12
//	   -- "read"
//	      -- Alice
//	      -- Bob
//	   -- "write"
//	      -- Bob
//	   -- "delete"
//	      -- Bob
//
type snapCred struct {
	key    []byte
	action string
}

func (sc snapCred) GetID() []byte {
	return append([]byte{}, sc.key[:4]...)
}

func (cs snapCred) GetRule() string {
	return cs.action
}

It could be optimized for example by performing the checks in batches. We could also express rules differently with "*" or "R/W", or by including the contract to express rules like "User Bob can write key X for contract A but only read key X for contract B":

-- 0xabcdef12
  -- contractA:read
    -- Bob
  -- contractB:write
    -- Bob

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants