bindbox-game/docs/赠送资产漏洞核查报告.md
win 8d1eef2f7f fix(channel): 修复渠道统计GMV重复计数和商城直购误计入
1. 排除商城直购(source_type=1):GMV和成本过滤条件从IN(1,2,3,4)改为IN(2,3,4)
2. 排除次卡免费使用订单(actual_amount=0):避免购买次卡和使用次卡双重计入GMV
   - source_type=4 一番赏使用次卡:1578单 44032元重复
   - source_type=3 对对碰使用次卡:422单 7042元重复
   - 合计去除51074元虚增GMV(29.1%)
3. 成本过滤条件同步修正:source_type IN(2,3,4),total_amount>0

修正后:GMV从175600降至124527元,毛利率从37.4%回到真实的11.8%
2026-03-16 21:41:39 +08:00

163 lines
7.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 赠送资产漏洞核查报告
> 数据源: dev_game 数据库 | 核查日期: 2026-03-11
---
## 一、结论摘要
| 项目 | 结论 |
|------|------|
| 并发漏洞 | **确实存在**已修复SELECT FOR UPDATE + RowsAffected 检查) |
| 实际货物损失(一份发两份) | **0 元** — 18 个重复发货资产中,没有一个真正被两方都发了货 |
| 积分重复兑换 | **0 元** — 没有任何资产被多人兑换积分,也没有同一资产被兑换多次 |
| 发送方转赠后又兑换同一资产 | **0 笔** — 发送方没有在转赠后兑换过同一资产 |
| 转赠后接收方兑换积分 | 91 笔 / 12,088.80 元 — **合法行为**,资产转赠后归接收方所有 |
**总实际损失: 0 元**
---
## 二、漏洞技术分析
### 2.1 Bug 描述
文件: `internal/service/user/address_share.go`
| Bug | 位置 | 描述 | 后果 |
|-----|------|------|------|
| readDB 竞态 | 原 L116-133 | 反重复检查使用从库readDB主从延迟 10-100ms 内并发请求可绕过 | 同一资产产生重复转赠记录和重复发货记录 |
| RowsAffected 未检查 | 原 L181-189 | `Updates()` 返回 0 行影响时不报错,后续操作继续执行 | 资产状态未变但发货记录已创建 |
### 2.2 修复方案(已合并 zuncle 分支)
| 修复 | 方式 |
|------|------|
| 竞态条件 | 在事务内使用 `SELECT FOR UPDATE` 锁行 + 写库查询发货记录 |
| RowsAffected | 转赠和原主发货两个分支都检查 `result.RowsAffected == 0` 后回滚 |
### 2.3 关于"转赠资产禁止兑换积分"
zuncle 分支原本还包含第三个修复:禁止通过转赠获得的资产兑换积分。经核实,**这是业务策略而非修漏洞**
- 资产转赠后归接收方所有,接收方有权决定发货或兑换积分
- 发送方没有对已转赠的资产做任何兑换操作
- 不存在"一份资产两边都兑换"的情况
**已回退此限制。**
---
## 三、数据核查
### 3.1 全局概况
| 指标 | 数值 |
|------|------|
| 总转赠记录数 | 157 条 |
| 涉及资产数 | 135 个 |
| 涉及用户数 | 15 人 |
| 多次转赠资产 | 13 个(并发 bug 导致的重复记录) |
| 两方都产生发货记录的资产 | 18 个 |
### 3.2 重复发货资产明细18 个)
> 同一资产在发送方和接收方名下都产生了发货记录
#### 仅接收方有效发货发送方全取消8 个)— 无损失
| 资产ID | 价值(元) | 发送方 | 接收方 | 接收方状态 | 发送方状态 | 兑换积分 |
|--------|---------|--------|--------|-----------|-----------|---------|
| 49426 | 1,315.00 | 9248(不出last退了) | 9116(非洲人) | 有效1/取消1 | 全取消(2) | 否 |
| 47096 | 900.00 | 9305(新人) | 9116(非洲人) | 有效1/取消1 | 全取消(3) | 否 |
| 51038 | 605.00 | 9210(非酋) | 9116(非洲人) | 有效1/取消3 | 全取消(1) | 否 |
| 44153 | 375.00 | 9248(不出last退了) | 9116(非洲人) | 有效1/取消2 | 全取消(1) | 否 |
| 44152 | 375.00 | 9248(不出last退了) | 9116(非洲人) | 有效1/取消4 | 全取消(1) | 否 |
| 44151 | 375.00 | 9248(不出last退了) | 9116(非洲人) | 有效1/取消2 | 全取消(1) | 否 |
| 44150 | 375.00 | 9248(不出last退了) | 9116(非洲人) | 有效1/取消2 | 全取消(1) | 否 |
小计: 4,320 元,全部仅接收方 9116 有效发货,**无实际损失**。
#### 双方全取消10 个)— 无损失
| 资产ID | 价值(元) | 发送方 | 接收方 | 接收方兑换积分 |
|--------|---------|--------|--------|------------|
| 42746 | 375.00 | 9336(有冰的帝君) | 9116(非洲人) | 是 |
| 42757 | 375.00 | 9336(有冰的帝君) | 9116(非洲人) | 是 |
| 42758 | 375.00 | 9336(有冰的帝君) | 9116(非洲人) | 是 |
| 43304 | 375.00 | 9305(新人) | 9116(非洲人) | 是 |
| 42761 | 375.00 | 9116(非洲人) | 9305(新人) | 是 |
| 46445 | 375.00 | 9230(巨欧小肥龙) | 9116(非洲人) | 是 |
| 46446 | 375.00 | 9230(巨欧小肥龙) | 9116(非洲人) | 是 |
| 46447 | 375.00 | 9230(巨欧小肥龙) | 9116(非洲人) | 是 |
| 46506 | 375.00 | 9209(程c) | 9116(非洲人) | 是 |
| 46507 | 375.00 | 9209(程c) | 9116(非洲人) | 是 |
| 52338 | 12.50 | 9094(范巴斯滕) | 9449(古利特) | 是 |
小计: 双方发货全取消,接收方后续兑换了积分 — 这属于**接收方对自有资产的合法操作**。
### 3.3 积分兑换核查
| 核查项 | 结果 |
|--------|------|
| 同一资产被多个用户兑换积分 | **0 个** |
| 同一资产被同一用户多次兑换积分 | **0 个** |
| 发送方在 points_ledger 中兑换已转赠资产 | **0 笔** |
| 发送方的已转赠资产 remark 含 redeemed | **1 笔**(用户 9116 转出给 9305 的资产 43304后又转回 9116 兑换,属于正常来回转赠) |
### 3.4 转赠后接收方兑换积分明细
> 以下为合法行为,资产转赠后归接收方所有,接收方有权兑换
| 用户ID | 昵称 | 兑换笔数 | 兑换金额(元) | 性质 |
|--------|------|---------|------------|------|
| 9116 | 非洲人 | 30 | 10,737.00 | 合法 — 接收转赠后兑换 |
| 9110 | 极品官方内部号 | 24 | 446.60 | 合法 |
| 9305 | 新人 | 1 | 375.00 | 合法 |
| 9209 | 程c | 9 | 220.00 | 合法 |
| 9222 | 嗯?!!!! | 15 | 180.00 | 合法 |
| 9336 | 有冰的帝君 | 1 | 60.00 | 合法 |
| 9094 | 范巴斯滕救了一个美女 | 2 | 25.00 | 合法 |
| 9210 | 非酋 | 3 | 22.50 | 合法 |
| 9449 | 古利特使出了佛怒火莲 | 1 | 12.50 | 合法 |
| 9248 | 不出last退了 | 3 | 8.60 | 合法 |
| 9365 | 未命名 | 1 | 1.00 | 合法 |
| 9230 | 巨欧小肥龙 | 1 | 0.60 | 合法 |
| **合计** | | **91** | **12,088.80** | **全部合法** |
### 3.5 多次转赠记录(并发 bug 产生的脏数据)
| 资产ID | 转赠次数 | 路径 | 说明 |
|--------|---------|------|------|
| 42746 | 4 | 9336→9116 x4 | 同一操作并发触发4次 |
| 46668 | 4 | 9210→9116 x4 | 同上 |
| 43304 | 3 | 9305→9116, 9116→9305, 9305→9116 | 正常来回转赠 |
| 44152 | 3 | 9248→9116 x3 | 并发触发3次 |
| 46445 | 3 | 9230→9116 x3 | 并发触发3次 |
| 46667 | 3 | 9210→9116 x3 | 并发触发3次 |
| 51038 | 3 | 9210→9116, 9116→9210, 9210→9116 | 正常来回转赠 |
| 其他 6 个 | 2 | — | 并发触发2次 |
这些重复记录是脏数据,但未造成资产复制或积分重复。
---
## 四、修复状态
| 修复项 | 状态 | Commit |
|--------|------|--------|
| SELECT FOR UPDATE 防并发转赠 | ✅ 已合并 main | `8229b41` |
| RowsAffected 检查防静默失败 | ✅ 已合并 main | `8229b41` |
| ~~禁止转赠资产兑换积分~~ | ❌ 已回退 | `749464c`,属业务策略非 bug 修复 |
---
## 五、结论
1. **并发 bug 确实存在**readDB 竞态 + RowsAffected 未检查,导致同一资产产生重复转赠记录和重复发货记录
2. **实际经济损失为 0 元**
- 没有任何资产被真正发了两份货(重复发货记录中,发送方一侧全部已取消)
- 没有任何资产被重复兑换积分
- 发送方没有在转赠后又兑换同一资产的积分
3. **转赠后兑换积分是合法行为**:资产转赠后归接收方,接收方有权兑换
4. **Bug 已修复**,防止未来可能的真正损失