pingcap / pingcap/tidb

[Column Masking] BLOCKER: BatchPointGet fast path bypasses masking policy

Open
#67,042 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Go
Stars
40.5k
Forks
6.2k
PR merge metrics
PR metrics pending

Description

## Severity
BLOCKER

## Description
Any query that hits `BatchPointGetPlan` (typically `WHERE pk IN (...)` or unique key `IN`) returns original column values without masking. The current implementation only adds `MaskingExprs` to `PointGet`, but the `BatchPointGet` path has no corresponding mechanism.

## Impact
Column masking policy can be bypassed by simply rewriting SQL form (change `=` to `IN`), which is a security/compliance level feature failure.

## Example
```sql
-- Normal masking works
SELECT sensitive_col FROM t WHERE pk = 1; -- returns masked value

-- Bypasses masking
SELECT sensitive_col FROM t WHERE pk IN (1); -- returns original value!
```

## Related Code Locations
- `pkg/planner/core/point_get_plan.go:940` - fast plan prefers BatchPointGetPlan
- `pkg/planner/optimize.go:240` - optimize returns fast plan directly
- `pkg/planner/core/point_get_plan.go:442` - BatchPointGetPlan has no MaskingExprs field
- `pkg/executor/builder.go:5443` - building BatchPointGetExec without masking
- `pkg/executor/batch_point_get.go:196` - Next only decodes and returns rows

## Proposed Fix
**Option A (Recommended)**: Add `MaskingExprs` to `BatchPointGetPlan`/`BatchPointGetExec` similar to `PointGet`, and evaluate/replace output columns row-by-row before `Next` returns.

**Option B**: When target table/output columns have enabled masking policies, disable `tryWhereIn2BatchPointGet` (return nil), fallback to normal planner path.

## Parent Issue
Relates to #65744

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.