From 0158fd712c7ece52e4552e113d90e0ffe97d80a4 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Fri, 3 Jul 2026 19:28:47 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20Fix=20infin?= =?UTF-8?q?ite=20loop=20DoS=20vulnerabilities=20in=20mirt=20estimation=20r?= =?UTF-8?q?etries?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - R/aFIPC.R: `oldFormModel`과 `newFormModel`을 추정할 때 `mirt::mirt()`가 지속적으로 실패하여 `try()` 블록을 빠져나오지 못할 경우 발생하는 무한 루프(Denial of Service)를 방지하기 위해 `max_retries` 제한을 추가했습니다. - .jules/sentinel.md: `try()`와 `while(!exists(...))` 패턴의 위험성과 관련 보안 학습 내용을 기록했습니다. --- .jules/sentinel.md | 4 ++++ R/aFIPC.R | 16 ++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/.jules/sentinel.md b/.jules/sentinel.md index 8b7813b..0e56ff5 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -9,3 +9,7 @@ 5. DoS 완화를 위해 `return(1L)` 같은 기본 승인값을 넣을 때는 추정 기준척도, anchor/common item, true parameter 재현 계약을 우회하지 않는지 먼저 검증합니다. 6. Fail-secure 에러 메시지는 테스트의 일부로 취급합니다. 보안 테스트는 실제 구현 메시지와 맞아야 하며, 오래된 `"Interactive prompt is not available"` 같은 별도 문구를 새로 만들지 않습니다. 7. Prompt DoS 회귀 테스트는 모델 추정 실패에 기대지 말고, common-item confirmation guard처럼 취약한 입력 경계에서 바로 발생하는 fail-secure 에러를 검증합니다. +## 2024-07-03 - [Preventing Denial of Service (DoS) with Infinite Retries] +**Vulnerability:** Found unbounded `while (!exists('var'))` loops used alongside `try()` inside `R/aFIPC.R`. If the operation inside `try()` consistently fails, this creates an infinite loop resulting in a Denial of Service. +**Learning:** `try()` constructs combined with existence checks on the assigned variable are a common but risky pattern for retrying flaky procedures like parameter estimation in R. Without bounds, these loops will block the process forever on unrecoverable failures. +**Prevention:** Always include a `max_retries` counter in retry loops. If `max_retries` is exhausted, explicitly stop execution with `stop("Max retries reached")`. diff --git a/R/aFIPC.R b/R/aFIPC.R index d0329f2..2325f2a 100644 --- a/R/aFIPC.R +++ b/R/aFIPC.R @@ -216,7 +216,10 @@ autoFIPC <- ) try(rm(oldFormModel)) - while (!exists('oldFormModel')) { + max_retries <- 5 + retries <- 0 + while (!exists('oldFormModel') && retries < max_retries) { + retries <- retries + 1 try( oldFormModel <- mirt::mirt( @@ -231,6 +234,9 @@ autoFIPC <- ) ) } + if (!exists('oldFormModel')) { + stop("oldFormModel estimation failed after maximum retries.") + } } } @@ -428,7 +434,10 @@ autoFIPC <- ) try(rm(newFormModel)) - while (!exists('newFormModel')) { + max_retries <- 5 + retries <- 0 + while (!exists('newFormModel') && retries < max_retries) { + retries <- retries + 1 try( newFormModel <- mirt::mirt( @@ -443,6 +452,9 @@ autoFIPC <- ) ) } + if (!exists('newFormModel')) { + stop("newFormModel estimation failed after maximum retries.") + } } }