fix(posts): 钳制 page 上限,防止无界 OFFSET 与缓存键扇出
clamp_pagination 原先仅限制 per_page,page 无上限。攻击者可用海量不同 page 值撑大缓存键空间(缓存污染)并触发无意义的超大 OFFSET 扫描。新增 MAX_PAGE=10_000,对任何实际博客都足够宽裕(配合 MAX_PER_PAGE 最多覆盖 50 万篇文章),同时把缓存键空间限制在有限范围。
This commit is contained in:
parent
6d32664020
commit
eaa7118e09
@ -20,11 +20,21 @@ use crate::db::pool::get_conn;
|
||||
/// 攻击者可传入巨大值迫使数据库扫描并实例化超大 Vec,造成内存放大与拒绝服务。
|
||||
const MAX_PER_PAGE: i32 = 50;
|
||||
|
||||
/// 将分页参数钳制到安全范围:页码至少为 1,每页 1–`MAX_PER_PAGE`。
|
||||
/// 允许的最大页码。
|
||||
///
|
||||
/// `page` 无上限时,攻击者可用海量不同 `page` 值撑大缓存键空间(缓存污染),
|
||||
/// 并触发无意义的超大 `OFFSET` 扫描。10_000 对任何实际博客都足够宽裕
|
||||
/// (配合 `MAX_PER_PAGE` 最多覆盖 50 万篇文章),同时把缓存键空间限制在有限范围。
|
||||
const MAX_PAGE: i32 = 10_000;
|
||||
|
||||
/// 将分页参数钳制到安全范围:页码 1–`MAX_PAGE`,每页 1–`MAX_PER_PAGE`。
|
||||
///
|
||||
/// 注意:返回值必须同时用于缓存键与 SQL 查询,避免同一逻辑页落入不同缓存条目。
|
||||
fn clamp_pagination(page: i32, per_page: i32) -> (i32, i32) {
|
||||
(page.max(1), per_page.clamp(1, MAX_PER_PAGE))
|
||||
(
|
||||
page.clamp(1, MAX_PAGE),
|
||||
per_page.clamp(1, MAX_PER_PAGE),
|
||||
)
|
||||
}
|
||||
|
||||
/// 获取已发布文章分页列表。
|
||||
@ -243,6 +253,19 @@ mod tests {
|
||||
assert_eq!(clamp_pagination(1, -100), (1, 1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clamp_pagination_clamps_oversized_page() {
|
||||
// 巨大 page 必须被压回上限,避免无界 OFFSET 扫描与缓存键扇出。
|
||||
assert_eq!(clamp_pagination(i32::MAX, 10), (MAX_PAGE, 10));
|
||||
assert_eq!(clamp_pagination(MAX_PAGE + 1, 10), (MAX_PAGE, 10));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clamp_pagination_max_page_boundary() {
|
||||
assert_eq!(clamp_pagination(MAX_PAGE, 10), (MAX_PAGE, 10));
|
||||
assert_eq!(clamp_pagination(MAX_PAGE - 1, 10), (MAX_PAGE - 1, 10));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clamp_pagination_max_per_page_boundary() {
|
||||
assert_eq!(clamp_pagination(1, MAX_PER_PAGE), (1, MAX_PER_PAGE));
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user