Skip to content

Commit

Permalink
Fix a deadlock if a panic happens during request handling
Browse files Browse the repository at this point in the history
During request handling, if a panic is created, deferred functions are
run but otherwise execution stops. #5889 changed some locks to
non-defers but had the side effect of causing the read lock to not be
released if the request panicked. This fixes that and addresses a few
other potential places where things could go wrong:

1) In sealInitCommon we always now defer a function that unlocks the
read lock if it hasn't been unlocked already
2) In StepDown we defer the RUnlock but we also had two error cases that
were calling it manually. These are unlikely to be hit but if they were
I believe would cause a panic.
  • Loading branch information
jefferai committed Jun 19, 2019
1 parent e657dc4 commit 9dc331a
Show file tree
Hide file tree
Showing 3 changed files with 10 additions and 20 deletions.
17 changes: 8 additions & 9 deletions vault/core.go
Expand Up @@ -1193,9 +1193,15 @@ func (c *Core) Seal(token string) error {
func (c *Core) sealInitCommon(ctx context.Context, req *logical.Request) (retErr error) {
defer metrics.MeasureSince([]string{"core", "seal-internal"}, time.Now())

var unlocked bool
defer func() {
if !unlocked {
c.stateLock.RUnlock()
}
}()

if req == nil {
retErr = multierror.Append(retErr, errors.New("nil request to seal"))
c.stateLock.RUnlock()
return retErr
}

Expand All @@ -1207,14 +1213,12 @@ func (c *Core) sealInitCommon(ctx context.Context, req *logical.Request) (retErr
if c.standby {
c.logger.Error("vault cannot seal when in standby mode; please restart instead")
retErr = multierror.Append(retErr, errors.New("vault cannot seal when in standby mode; please restart instead"))
c.stateLock.RUnlock()
return retErr
}

acl, te, entity, identityPolicies, err := c.fetchACLTokenEntryAndEntity(ctx, req)
if err != nil {
retErr = multierror.Append(retErr, err)
c.stateLock.RUnlock()
return retErr
}

Expand Down Expand Up @@ -1242,20 +1246,17 @@ func (c *Core) sealInitCommon(ctx context.Context, req *logical.Request) (retErr
if err := c.auditBroker.LogRequest(ctx, logInput, c.auditedHeaders); err != nil {
c.logger.Error("failed to audit request", "request_path", req.Path, "error", err)
retErr = multierror.Append(retErr, errors.New("failed to audit request, cannot continue"))
c.stateLock.RUnlock()
return retErr
}

if entity != nil && entity.Disabled {
c.logger.Warn("permission denied as the entity on the token is disabled")
retErr = multierror.Append(retErr, logical.ErrPermissionDenied)
c.stateLock.RUnlock()
return retErr
}
if te != nil && te.EntityID != "" && entity == nil {
c.logger.Warn("permission denied as the entity on the token is invalid")
retErr = multierror.Append(retErr, logical.ErrPermissionDenied)
c.stateLock.RUnlock()
return retErr
}

Expand All @@ -1266,13 +1267,11 @@ func (c *Core) sealInitCommon(ctx context.Context, req *logical.Request) (retErr
if err != nil {
c.logger.Error("failed to use token", "error", err)
retErr = multierror.Append(retErr, ErrInternalError)
c.stateLock.RUnlock()
return retErr
}
if te == nil {
// Token is no longer valid
retErr = multierror.Append(retErr, logical.ErrPermissionDenied)
c.stateLock.RUnlock()
return retErr
}
}
Expand All @@ -1282,7 +1281,6 @@ func (c *Core) sealInitCommon(ctx context.Context, req *logical.Request) (retErr
RootPrivsRequired: true,
})
if !authResults.Allowed {
c.stateLock.RUnlock()
retErr = multierror.Append(retErr, authResults.Error)
if authResults.Error.ErrorOrNil() == nil || authResults.DeniedError {
retErr = multierror.Append(retErr, logical.ErrPermissionDenied)
Expand All @@ -1304,6 +1302,7 @@ func (c *Core) sealInitCommon(ctx context.Context, req *logical.Request) (retErr
}

// Unlock; sealing will grab the lock when needed
unlocked = true
c.stateLock.RUnlock()

sealErr := c.sealInternal()
Expand Down
3 changes: 1 addition & 2 deletions vault/ha.go
Expand Up @@ -207,6 +207,7 @@ func (c *Core) StepDown(httpCtx context.Context, req *logical.Request) (retErr e

c.stateLock.RLock()
defer c.stateLock.RUnlock()

if c.Sealed() {
return nil
}
Expand Down Expand Up @@ -261,14 +262,12 @@ func (c *Core) StepDown(httpCtx context.Context, req *logical.Request) (retErr e
if entity != nil && entity.Disabled {
c.logger.Warn("permission denied as the entity on the token is disabled")
retErr = multierror.Append(retErr, logical.ErrPermissionDenied)
c.stateLock.RUnlock()
return retErr
}

if te != nil && te.EntityID != "" && entity == nil {
c.logger.Warn("permission denied as the entity on the token is invalid")
retErr = multierror.Append(retErr, logical.ErrPermissionDenied)
c.stateLock.RUnlock()
return retErr
}

Expand Down
10 changes: 1 addition & 9 deletions vault/request_handling.go
Expand Up @@ -385,18 +385,12 @@ func (c *Core) HandleRequest(httpCtx context.Context, req *logical.Request) (res
func (c *Core) switchedLockHandleRequest(httpCtx context.Context, req *logical.Request, doLocking bool) (resp *logical.Response, err error) {
if doLocking {
c.stateLock.RLock()
}
unlockFunc := func() {
if doLocking {
c.stateLock.RUnlock()
}
defer c.stateLock.RUnlock()
}
if c.Sealed() {
unlockFunc()
return nil, consts.ErrSealed
}
if c.standby && !c.perfStandby {
unlockFunc()
return nil, consts.ErrStandby
}

Expand All @@ -412,7 +406,6 @@ func (c *Core) switchedLockHandleRequest(httpCtx context.Context, req *logical.R
ns, err := namespace.FromContext(httpCtx)
if err != nil {
cancel()
unlockFunc()
return nil, errwrap.Wrapf("could not parse namespace from http context: {{err}}", err)
}
ctx = namespace.ContextWithNamespace(ctx, ns)
Expand All @@ -421,7 +414,6 @@ func (c *Core) switchedLockHandleRequest(httpCtx context.Context, req *logical.R

req.SetTokenEntry(nil)
cancel()
unlockFunc()
return resp, err
}

Expand Down

0 comments on commit 9dc331a

Please sign in to comment.