fix permission delegation vulnerability#5825
Conversation
da6ec15 to
3db5026
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #5825 +/- ##
=========================================
- Coverage 78.3% 78.3% -0.0%
=========================================
Files 817 817
Lines 69016 69003 -13
Branches 8320 8322 +2
=========================================
- Hits 54038 54023 -15
- Misses 14978 14980 +2
🚀 New features to boost your workflow:
|
|
@yinyiqian1 there are some merge conflicts here |
|
Blocked on waiting for the 3.0 release, will be merged after |
|
Since the 3.0.0 release branch has already been cut, we can go ahead and merge this one. |
|
/ai-review |
| TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete, | ||
| Delegation::notDelegatable, | ||
| uint256{}, | ||
| featureDeletableAccounts, |
There was a problem hiding this comment.
AccountDelete feature guard changed - verify delegation behavior across feature boundary:
There was a problem hiding this comment.
In the latest develop branch, featureDeletableAccounts has been removed, so this is no longer a concern.
|
|
||
| if (!sle) | ||
| return tecNO_DELEGATE_PERMISSION; | ||
| return terNO_DELEGATE_PERMISSION; |
There was a problem hiding this comment.
Permission-denied transactions now return ter instead of tec - prevents fee-free spam:
There was a problem hiding this comment.
We intended to use NotTEC, because this is called before checkSign
There was a problem hiding this comment.
Good point — since this is called before checkSign, using NotTEC (a ter code) is correct here. ter results are retried rather than applied to the ledger, which makes sense for a pre-signing permission check. Thanks for the clarification; I'll update my understanding. This is the right approach.
| tecLIMIT_EXCEEDED = 195, | ||
| tecPSEUDO_ACCOUNT = 196, | ||
| tecPRECISION_LOSS = 197, | ||
| // DEPRECATED: This error code tecNO_DELEGATE_PERMISSION is reserved for |
There was a problem hiding this comment.
Semantic mismatch: ter error suggests retry when permission failures are permanent. Add client documentation:
There was a problem hiding this comment.
This is a duplicate comment(the same as the next one), #6675
There was a problem hiding this comment.
Thanks for pointing that out! You're right that this is a duplicate of the same concern raised in the linked comment. I'll defer to the discussion in that thread to avoid redundancy.
| tecLIMIT_EXCEEDED = 195, | ||
| tecPSEUDO_ACCOUNT = 196, | ||
| tecPRECISION_LOSS = 197, | ||
| // DEPRECATED: This error code tecNO_DELEGATE_PERMISSION is reserved for |
There was a problem hiding this comment.
Deprecated error code lacks clear reclamation path. Add tracking mechanism:
There was a problem hiding this comment.
The comment is sufficient
There was a problem hiding this comment.
A comment alone doesn't provide a structured tracking mechanism for deprecation. In a production blockchain protocol like rippled, deprecated error codes need careful lifecycle management to avoid breakage in client integrations. Consider at minimum adding a // TODO(deprecation): Remove after featurePermissionDelegationV1_1 is in majority style comment with a concrete removal condition, or tracking it in the project's issue tracker. A plain "deprecated" comment doesn't signal when or how the code will be removed.
|
|
||
| if (!sle) | ||
| return tecNO_DELEGATE_PERMISSION; | ||
| return terNO_DELEGATE_PERMISSION; |
There was a problem hiding this comment.
Permission-denied payment transactions return ter, enabling potential fee-free transaction spam:
There was a problem hiding this comment.
We intended to return NotTEC, because this is returned before the checkSign.
pre-sign checks must be NotTEC to prevent fee extraction without key ownership.
There was a problem hiding this comment.
Good point — returning NotTEC before checkSign is the correct behavior to prevent fee extraction without key ownership. Thanks for the explanation. My concern was misplaced here since the normal spam protection applies: without a valid signature, the transaction won't get through the network in the first place. I'll withdraw my original concern.
| if ((tx.isFieldPresent(sfSendMax) && | ||
| tx[sfSendMax].asset() != amountAsset) || | ||
| tx.isFieldPresent(sfPaths)) | ||
| return terNO_DELEGATE_PERMISSION; |
There was a problem hiding this comment.
MPT payment authorization logic expanded without explicit guard:
There was a problem hiding this comment.
false alarm: PaymentMint and PaymentBurn apply to both IOU and MPT direct payments.
Authorization logic is in doApply.
|
|
||
| result = T::checkFee(ctx, calculateBaseFee(ctx.view, ctx.tx)); | ||
| if (NotTEC const result = | ||
| T::checkPermission(ctx.view, ctx.tx)) |
There was a problem hiding this comment.
Fee check moved after permission validation - potential DoS vector with fee-free invalid submissions:
There was a problem hiding this comment.
We intended to group the NotTEC functions together.
| NotTEC | ||
| checkTxPermission(std::shared_ptr<SLE const> const& delegate, STTx const& tx) | ||
| { | ||
| if (!delegate) |
There was a problem hiding this comment.
LCOV_EXCL_LINE suppresses coverage on security-critical null check. Add explicit unit tests:
There was a problem hiding this comment.
Thanks for the reference! That PR #6675 appears to add the unit tests for this security-critical path. Once those tests are in place, the LCOV_EXCL_LINE suppression would be justified. Could you confirm that PR #6675 covers the null check at line 28 specifically, and that it will be merged alongside or before this change?
There was a problem hiding this comment.
The test case in #6675 is used to cover the if (!delegate) null check path in DelegateUtils.cpp:
void
testDelegateUtilsNullptrCheck()
{
testcase("DelegateUtils nullptr check");
// checkTxPermission nullptr check
STTx const tx{ttPAYMENT, [](STObject&) {}};
BEAST_EXPECT(checkTxPermission(nullptr, tx) == terNO_DELEGATE_PERMISSION);
// loadGranularPermission nullptr check
std::unordered_set<GranularPermissionType> granularPermissions;
loadGranularPermission(nullptr, ttPAYMENT, granularPermissions);
BEAST_EXPECT(granularPermissions.empty());
}
|
|
||
| if (!sle) | ||
| return tecNO_DELEGATE_PERMISSION; | ||
| return terNO_DELEGATE_PERMISSION; |
There was a problem hiding this comment.
Permission-denied MPTokenIssuanceSet transactions return ter, enabling potential fee-free transaction spam:
There was a problem hiding this comment.
We intended to use NotTEC, because this is called before checkSign
There was a problem hiding this comment.
Thanks for the context! You're right that NotTEC (a TER result) is appropriate here when called before checkSign, since the transaction hasn't been signed yet and fee charging hasn't been determined. Returning a TER at this stage is the correct pattern to avoid charging fees for transactions that fail early validation. I'll withdraw my concern.
| @@ -3946,14 +3946,13 @@ class Batch_test : public beast::unit_test::suite | |||
| tesSUCCESS, | |||
There was a problem hiding this comment.
Test case silently drops validation of a failed inner Batch transaction
The test previously validated that the second inner transaction in a Batch (jv2, seq+2) was recorded in the ledger with tecNO_DELEGATE_PERMISSION. After changing to terNO_DELEGATE_PERMISSION, the validation entry is simply removed. The absence of any assertion means the test no longer explicitly verifies what happens to the second inner transaction.
Suggested fix: Add an explicit assertion that the second inner transaction is NOT present in the closed ledger to preserve test coverage for the 'permission denied inside a Batch' path.
There was a problem hiding this comment.
#6675 added:
std::vector<TestLedgerData> testCases = {
{0, "Batch", "tesSUCCESS", batchID, std::nullopt},
{1, "TrustSet", "tesSUCCESS", txIDs[0], batchID},
// jv2 fails with terNO_DELEGATE_PERMISSION.
};
validateClosedLedger(env, testCases);
// verify jv2 is not present in the closed ledger.
BEAST_EXPECT(
env.rpc("tx", txIDs[1])[jss::result][jss::error] ==
"txnNotFound");
|
The AI comments are addressed in a separate PR: #6675 |
New Amendment: Introduces the
featurePermissionDelegationV1_1amendment.Amendment Replacement: This new amendment is designed to supersede both
featurePermissionDelegationandfixDelegateV1_1, which should be considered deprecated.Error Code Correction: The checkPermission function will now return
terNO_DELEGATE_PERMISSIONwhen a delegate transaction lacks the necessary permissions.High Level Overview of Change
Context of Change
Type of Change
.gitignore, formatting, dropping support for older tooling)API Impact
libxrplchange (any change that may affectlibxrplor dependents oflibxrpl)