Skip to content

Commit

Permalink
verifycid: obliterate globals
Browse files Browse the repository at this point in the history
  • Loading branch information
Wondertan committed Aug 2, 2023
1 parent 8e41b53 commit cb902a7
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 163 deletions.
105 changes: 68 additions & 37 deletions blockservice/blockservice.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// package blockservice implements a BlockService interface that provides
// Package blockservice implements a BlockService interface that provides
// a single GetBlock/AddBlock interface that seamlessly retrieves data either
// locally or from a remote peer through the exchange.
package blockservice
Expand All @@ -11,11 +11,11 @@ import (
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"

blockstore "github.com/ipfs/boxo/blockstore"
exchange "github.com/ipfs/boxo/exchange"
"github.com/ipfs/boxo/blockstore"
"github.com/ipfs/boxo/exchange"
"github.com/ipfs/boxo/verifcid"
blocks "github.com/ipfs/go-block-format"
cid "github.com/ipfs/go-cid"
"github.com/ipfs/go-cid"
ipld "github.com/ipfs/go-ipld-format"
logging "github.com/ipfs/go-log/v2"

Expand Down Expand Up @@ -64,41 +64,61 @@ type BlockService interface {
DeleteBlock(ctx context.Context, o cid.Cid) error
}

// BoundedBlockService is a Blockservice bounded via strict multihash Allowlist.
type BoundedBlockService interface {
BlockService

Allowlist() verifcid.Allowlist
}

type blockService struct {
allowlist verifcid.Allowlist
blockstore blockstore.Blockstore
exchange exchange.Interface
// If checkFirst is true then first check that a block doesn't
// already exist to avoid republishing the block on the exchange.
checkFirst bool
}

// NewBlockService creates a BlockService with given datastore instance.
func New(bs blockstore.Blockstore, rem exchange.Interface) BlockService {
if rem == nil {
// New creates a BlockService with given datastore instance.
func New(bs blockstore.Blockstore, exchange exchange.Interface) BlockService {
if exchange == nil {
logger.Debug("blockservice running in local (offline) mode.")
}

return &blockService{
allowlist: verifcid.DefaultAllowlist,
blockstore: bs,
exchange: rem,
exchange: exchange,
checkFirst: true,
}
}

// NewWriteThrough creates a BlockService that guarantees writes will go
// through to the blockstore and are not skipped by cache checks.
func NewWriteThrough(bs blockstore.Blockstore, rem exchange.Interface) BlockService {
if rem == nil {
func NewWriteThrough(bs blockstore.Blockstore, exchange exchange.Interface) BlockService {
if exchange == nil {
logger.Debug("blockservice running in local (offline) mode.")
}

return &blockService{
allowlist: verifcid.DefaultAllowlist,
blockstore: bs,
exchange: rem,
exchange: exchange,
checkFirst: false,
}
}

// NewWithAllowList creates a BlockService with customized multihash Allowlist.
func NewWithAllowList(bs blockstore.Blockstore, exchange exchange.Interface, allowlist verifcid.Allowlist) BlockService {
return &blockService{
allowlist: allowlist,
blockstore: bs,
exchange: exchange,
checkFirst: true,
}
}

// Blockstore returns the blockstore behind this blockservice.
func (s *blockService) Blockstore() blockstore.Blockstore {
return s.blockstore
Expand All @@ -109,27 +129,37 @@ func (s *blockService) Exchange() exchange.Interface {
return s.exchange
}

func (s *blockService) Allowlist() verifcid.Allowlist {
return s.allowlist
}

// NewSession creates a new session that allows for
// controlled exchange of wantlists to decrease the bandwidth overhead.
// If the current exchange is a SessionExchange, a new exchange
// session will be created. Otherwise, the current exchange will be used
// directly.
func NewSession(ctx context.Context, bs BlockService) *Session {
allowlist := verifcid.DefaultAllowlist
if bbs, ok := bs.(BoundedBlockService); ok {
allowlist = bbs.Allowlist()
}
exch := bs.Exchange()
if sessEx, ok := exch.(exchange.SessionExchange); ok {
return &Session{
sessCtx: ctx,
ses: nil,
sessEx: sessEx,
bs: bs.Blockstore(),
notifier: exch,
allowlist: allowlist,
sessCtx: ctx,
ses: nil,
sessEx: sessEx,
bs: bs.Blockstore(),
notifier: exch,
}
}
return &Session{
ses: exch,
sessCtx: ctx,
bs: bs.Blockstore(),
notifier: exch,
allowlist: allowlist,
ses: exch,
sessCtx: ctx,
bs: bs.Blockstore(),
notifier: exch,
}
}

Expand All @@ -140,7 +170,7 @@ func (s *blockService) AddBlock(ctx context.Context, o blocks.Block) error {

c := o.Cid()
// hash security
err := verifcid.ValidateCid(c)
err := verifcid.ValidateCid(s.allowlist, c)
if err != nil {
return err
}
Expand Down Expand Up @@ -171,7 +201,7 @@ func (s *blockService) AddBlocks(ctx context.Context, bs []blocks.Block) error {

// hash security
for _, b := range bs {
err := verifcid.ValidateCid(b.Cid())
err := verifcid.ValidateCid(s.allowlist, b.Cid())
if err != nil {
return err
}
Expand Down Expand Up @@ -221,15 +251,15 @@ func (s *blockService) GetBlock(ctx context.Context, c cid.Cid) (blocks.Block, e
f = s.getExchange
}

return getBlock(ctx, c, s.blockstore, f) // hash security
return getBlock(ctx, c, s.blockstore, s.allowlist, f)
}

func (s *blockService) getExchange() notifiableFetcher {
return s.exchange
}

func getBlock(ctx context.Context, c cid.Cid, bs blockstore.Blockstore, fget func() notifiableFetcher) (blocks.Block, error) {
err := verifcid.ValidateCid(c) // hash security
func getBlock(ctx context.Context, c cid.Cid, bs blockstore.Blockstore, allowlist verifcid.Allowlist, fget func() notifiableFetcher) (blocks.Block, error) {
err := verifcid.ValidateCid(allowlist, c) // hash security
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -278,18 +308,18 @@ func (s *blockService) GetBlocks(ctx context.Context, ks []cid.Cid) <-chan block
f = s.getExchange
}

return getBlocks(ctx, ks, s.blockstore, f) // hash security
return getBlocks(ctx, ks, s.blockstore, s.allowlist, f) // hash security
}

func getBlocks(ctx context.Context, ks []cid.Cid, bs blockstore.Blockstore, fget func() notifiableFetcher) <-chan blocks.Block {
func getBlocks(ctx context.Context, ks []cid.Cid, bs blockstore.Blockstore, allowlist verifcid.Allowlist, fget func() notifiableFetcher) <-chan blocks.Block {
out := make(chan blocks.Block)

go func() {
defer close(out)

allValid := true
for _, c := range ks {
if err := verifcid.ValidateCid(c); err != nil {
if err := verifcid.ValidateCid(allowlist, c); err != nil {
allValid = false
break
}
Expand All @@ -300,7 +330,7 @@ func getBlocks(ctx context.Context, ks []cid.Cid, bs blockstore.Blockstore, fget
ks2 := make([]cid.Cid, 0, len(ks))
for _, c := range ks {
// hash security
if err := verifcid.ValidateCid(c); err == nil {
if err := verifcid.ValidateCid(allowlist, c); err == nil {
ks2 = append(ks2, c)
} else {
logger.Errorf("unsafe CID (%s) passed to blockService.GetBlocks: %s", c, err)
Expand Down Expand Up @@ -396,12 +426,13 @@ type notifier interface {

// Session is a helper type to provide higher level access to bitswap sessions
type Session struct {
bs blockstore.Blockstore
ses exchange.Fetcher
sessEx exchange.SessionExchange
sessCtx context.Context
notifier notifier
lk sync.Mutex
allowlist verifcid.Allowlist
bs blockstore.Blockstore
ses exchange.Fetcher
sessEx exchange.SessionExchange
sessCtx context.Context
notifier notifier
lk sync.Mutex
}

type notifiableFetcher interface {
Expand Down Expand Up @@ -444,15 +475,15 @@ func (s *Session) GetBlock(ctx context.Context, c cid.Cid) (blocks.Block, error)
ctx, span := internal.StartSpan(ctx, "Session.GetBlock", trace.WithAttributes(attribute.Stringer("CID", c)))
defer span.End()

return getBlock(ctx, c, s.bs, s.getFetcherFactory()) // hash security
return getBlock(ctx, c, s.bs, s.allowlist, s.getFetcherFactory()) // hash security
}

// GetBlocks gets blocks in the context of a request session
func (s *Session) GetBlocks(ctx context.Context, ks []cid.Cid) <-chan blocks.Block {
ctx, span := internal.StartSpan(ctx, "Session.GetBlocks")
defer span.End()

return getBlocks(ctx, ks, s.bs, s.getFetcherFactory()) // hash security
return getBlocks(ctx, ks, s.bs, s.allowlist, s.getFetcherFactory()) // hash security
}

var _ BlockGetter = (*Session)(nil)
13 changes: 11 additions & 2 deletions provider/reprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
"github.com/ipfs/boxo/verifcid"
"github.com/ipfs/go-cid"
"github.com/ipfs/go-datastore"
namespace "github.com/ipfs/go-datastore/namespace"
"github.com/ipfs/go-datastore/namespace"
logging "github.com/ipfs/go-log/v2"
"github.com/multiformats/go-multihash"
)
Expand Down Expand Up @@ -47,6 +47,7 @@ type reprovider struct {
initalReprovideDelay time.Duration
initialReprovideDelaySet bool

allowlist verifcid.Allowlist
rsys Provide
keyProvider KeyChanFunc

Expand Down Expand Up @@ -102,6 +103,7 @@ var DefaultKeyPrefix = datastore.NewKey("/provider")
// If provider casts to [Ready], it will wait until [Ready.Ready] is true.
func New(ds datastore.Batching, opts ...Option) (System, error) {
s := &reprovider{
allowlist: verifcid.DefaultAllowlist,
reprovideInterval: DefaultReproviderInterval,
maxReprovideBatchSize: math.MaxUint,
keyPrefix: DefaultKeyPrefix,
Expand Down Expand Up @@ -149,6 +151,13 @@ func New(ds datastore.Batching, opts ...Option) (System, error) {
return s, nil
}

func Allowlist(allowlist verifcid.Allowlist) Option {
return func(system *reprovider) error {
system.allowlist = allowlist
return nil
}
}

func ReproviderInterval(duration time.Duration) Option {
return func(system *reprovider) error {
system.reprovideInterval = duration
Expand Down Expand Up @@ -294,7 +303,7 @@ func (s *reprovider) run() {
delete(m, c)

// hash security
if err := verifcid.ValidateCid(c); err != nil {
if err := verifcid.ValidateCid(s.allowlist, c); err != nil {
log.Errorf("insecure hash in reprovider, %s (%s)", c, err)
continue
}
Expand Down
69 changes: 69 additions & 0 deletions verifcid/allowlist.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package verifcid

import (
mh "github.com/multiformats/go-multihash"
)

// DefaultAllowlist is the default list of hashes allowed in IPFS.
var DefaultAllowlist Allowlist = defaultAllowlist{}

// Allowlist defines an interface containing list of allowed multihashes.
type Allowlist interface {
// IsAllowed checks for multihash allowance by the code.
IsAllowed(code uint64) bool
}

// NewAllowlist constructs new [Allowlist] from the given map set.
func NewAllowlist(allowset map[uint64]bool) Allowlist {
return allowlist{allowset: allowset}
}

// NewOverridingAllowlist is like [NewAllowlist] but it will fallback to an other [AllowList] if keys are missing.
// If override is nil it will return unsecure for unknown things.
func NewOverridingAllowlist(override Allowlist, allowset map[uint64]bool) Allowlist {
return allowlist{override, allowset}
}

type allowlist struct {
override Allowlist
allowset map[uint64]bool
}

func (al allowlist) IsAllowed(code uint64) bool {
if good, found := al.allowset[code]; found {
return good
}

if al.override != nil {
return al.override.IsAllowed(code)
}

return false
}

type defaultAllowlist struct{}

func (defaultAllowlist) IsAllowed(code uint64) bool {
switch code {
case mh.SHA2_256, mh.SHA2_512,
mh.SHAKE_256,
mh.DBL_SHA2_256,
mh.BLAKE3,
mh.IDENTITY,

mh.SHA3_224, mh.SHA3_256, mh.SHA3_384, mh.SHA3_512,
mh.KECCAK_224, mh.KECCAK_256, mh.KECCAK_384, mh.KECCAK_512,

mh.SHA1: // not really secure but still useful for git
return true
default:
if code >= mh.BLAKE2B_MIN+19 && code <= mh.BLAKE2B_MAX {
return true
}
if code >= mh.BLAKE2S_MIN+19 && code <= mh.BLAKE2S_MAX {
return true
}

return false
}
}
Loading

0 comments on commit cb902a7

Please sign in to comment.