Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 4 additions & 11 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
## 2024-06-23 - ์žฌ๊ท€์  ์ž…๋ ฅ ๊ฒ€์ฆ์œผ๋กœ ์ธํ•œ ๋Œ€ํ™”ํ˜• ํ”„๋กฌํ”„ํŠธ DoS ์ทจ์•ฝ์ 
**Vulnerability:** ๋Œ€ํ™”ํ˜• ํ”„๋กฌํ”„ํŠธ ํ•จ์ˆ˜(`checkCorrect`, `checkoldformBILOGprior`, `checknewformBILOGprior`)์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์€ ๊ฐ’(์˜ˆ: ์ˆซ์ž๊ฐ€ ์•„๋‹Œ ๋ฌธ์ž์—ด)์„ ์ž…๋ ฅํ–ˆ์„ ๋•Œ, ๊ผฌ๋ฆฌ ์žฌ๊ท€(tail-recursive) ๋ฐฉ์‹์œผ๋กœ ์ž๊ธฐ ์ž์‹ ์„ ํ˜ธ์ถœํ•˜๋„๋ก ๊ตฌํ˜„๋˜์–ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” R์˜ C ์Šคํƒ ์‚ฌ์ด์ฆˆ ์ œํ•œ์œผ๋กœ ์ธํ•ด ์Šคํƒ ๊ณ ๊ฐˆ(Stack Exhaustion)๋กœ ์ด์–ด์ง€๋Š” DoS(์„œ๋น„์Šค ๊ฑฐ๋ถ€) ์ทจ์•ฝ์ ์„ ์•ผ๊ธฐํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, `readline()`์ด ๊ณ„์† ๋นˆ ๋ฌธ์ž์—ด์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋น„๋Œ€ํ™”ํ˜•(non-interactive) CI/CD ํ™˜๊ฒฝ์—์„œ๋Š” ๋ฌดํ•œ ๋ฃจํ”„๋ฅผ ๋ฐœ์ƒ์‹œ์ผฐ์Šต๋‹ˆ๋‹ค.
**Learning:** ์ž…๋ ฅ ๊ฒ€์ฆ ๋ฃจํ”„๋Š” ์žฌ๊ท€ ํ˜ธ์ถœ์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌํ˜„ํ•ด์„œ๋Š” ์•ˆ ๋˜๋ฉฐ, ํŠนํžˆ ์ž๋™ํ™”๋œ ํ™˜๊ฒฝ์—์„œ ์ž…๋ ฅ์ด ์ œ๊ณต๋  ๋•Œ ๋”์šฑ ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋น„๋Œ€ํ™”ํ˜• ์„ธ์…˜์—์„œ์˜ ํƒˆ์ถœ๊ตฌ(escape hatch)๊ฐ€ ์—†๋Š” ์žฌ๊ท€์  ์ž…๋ ฅ ๋ฃจํ”„๋Š” ๋ฆฌ์†Œ์Šค๋ฅผ ์ฆ‰์‹œ ๊ณ ๊ฐˆ์‹œ์ผœ ์ž๋™ํ™”๋œ ํ…Œ์ŠคํŠธ ๋ฐ CI ํ™˜๊ฒฝ์„ ์†์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.
**Prevention:**
1. ์ž…๋ ฅ ๊ฒ€์ฆ ์‹œ ์žฌ๊ท€ ํ˜ธ์ถœ ๋Œ€์‹  `repeat` ๋ฃจํ”„๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
2. ์ˆ˜๋™ ์ž…๋ ฅ์„ ์š”๊ตฌํ•˜๊ธฐ ์ „์— ํ•ญ์ƒ `!interactive()`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ˜„์žฌ ์„ธ์…˜์ด ๋Œ€ํ™”ํ˜•์ธ์ง€ ํ™•์ธํ•˜๊ณ , ๋น„๋Œ€ํ™”ํ˜• ์„ธ์…˜์ผ ๊ฒฝ์šฐ์—๋Š” ์ฆ‰์‹œ ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œ์ผœ ์•ˆ์ „ํ•˜๊ฒŒ ์‹คํŒจ(fail securely)ํ•˜๋„๋ก ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
3. common item ํ™•์ธ์ฒ˜๋Ÿผ ์ถ”์ • ๊ฒฐ๊ณผ์˜ ๊ธฐ์ค€์ฒ™๋„์™€ true parameter ์žฌํ˜„์— ์ง์ ‘ ์˜ํ–ฅ์„ ์ฃผ๋Š” ์ž…๋ ฅ์€ ๋น„๋Œ€ํ™”ํ˜• ํ™˜๊ฒฝ์—์„œ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์ž๋™ ์Šน์ธํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ž๋™ํ™”์—์„œ๋Š” `confirmCommonItems = TRUE`์ฒ˜๋Ÿผ ๋ช…์‹œ์  opt-in์„ ์š”๊ตฌํ•ฉ๋‹ˆ๋‹ค.
4. ๋Œ€ํ™”ํ˜• ์žฌ์ž…๋ ฅ ๋ฃจํ”„๋„ ๋ฌดํ•œ ๋ฐ˜๋ณตํ•˜์ง€ ์•Š๋„๋ก ์ œํ•œ๋œ ํšŸ์ˆ˜๋งŒ ํ—ˆ์šฉํ•˜๊ณ , ์ดˆ๊ณผ ์‹œ ๋ช…ํ™•ํ•œ ์—๋Ÿฌ๋กœ ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค.
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-01 - ๋ฌดํ•œ ๋ฃจํ”„ ์„œ๋น„์Šค ๊ฑฐ๋ถ€(DoS) ์ทจ์•ฝ์ 
**์ทจ์•ฝ์ :** ์žฌ์‹œ๋„ ์ œํ•œ์ด ์—†๋Š” `try()` ๋ธ”๋ก๊ณผ ๊ฒฐํ•ฉ๋œ `while (!exists(...))` ๊ฒ€์‚ฌ๋กœ ์ธํ•œ ๋ฌดํ•œ ๋ฃจํ”„ DoS.
**ํ•™์Šต:** ์‹คํŒจํ•  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๋Š” `try()` ์ž‘์—…(์˜ˆ: mirt ๋งค๊ฐœ๋ณ€์ˆ˜ ์ถ”์ •)์— ์˜ํ•ด ์ƒ์„ฑ๋œ ๊ฐ์ฒด์— ๋ณดํ˜ธ๋˜์ง€ ์•Š์€ `while (!exists(...))` ๋ฃจํ”„๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด DoS ์ทจ์•ฝ์ ์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์ถ”์ •์ด ์ง€์†์ ์œผ๋กœ ์‹คํŒจํ•˜๊ณ  ๊ฐ์ฒด๊ฐ€ ์ƒ์„ฑ๋˜์ง€ ์•Š์œผ๋ฉด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋ฌดํ•œ ๋ฃจํ”„์— ๋น ์ ธ CPU๋ฅผ ๊ณ ๊ฐˆ์‹œํ‚ค๊ณ  ์ค‘๋‹จ๋ฉ๋‹ˆ๋‹ค.
**์˜ˆ๋ฐฉ:** ํŠนํžˆ `try()` ๋ฐ `exists()`์™€ ๊ด€๋ จ๋œ ์ง€์†์ ์œผ๋กœ ์‹คํŒจํ•  ์ˆ˜ ์žˆ๋Š” ์ž‘์—…์„ ์žฌ์‹œ๋„ํ•˜๋Š” `while` ๋ฃจํ”„์—๋Š” ํ•ญ์ƒ `max_retries` ์นด์šดํ„ฐ๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
Comment on lines +1 to +4
16 changes: 14 additions & 2 deletions R/aFIPC.R
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,9 @@ autoFIPC <-
)

try(rm(oldFormModel))
while (!exists('oldFormModel')) {
retries <- 0
max_retries <- 10
while (!exists('oldFormModel') && retries < max_retries) {
try(
Comment on lines 218 to 222
oldFormModel <-
mirt::mirt(
Expand All @@ -230,6 +232,10 @@ autoFIPC <-
GenRandomPars = F
)
)
retries <- retries + 1
}
if (!exists('oldFormModel')) {
stop("Failed to estimate oldFormModel after maximum retries.")
}
}
}
Expand Down Expand Up @@ -428,7 +434,9 @@ autoFIPC <-
)

try(rm(newFormModel))
while (!exists('newFormModel')) {
retries <- 0
max_retries <- 10
while (!exists('newFormModel') && retries < max_retries) {
try(
Comment on lines 436 to 440
newFormModel <-
mirt::mirt(
Expand All @@ -442,6 +450,10 @@ autoFIPC <-
GenRandomPars = F
)
)
retries <- retries + 1
}
if (!exists('newFormModel')) {
stop("Failed to estimate newFormModel after maximum retries.")
}
}
}
Expand Down
29 changes: 29 additions & 0 deletions tests/testthat/test-dos-fix.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
test_that("max_retries prevents infinite loop in model estimation", {
skip_if_not_installed("mirt")

# create data that will cause model estimation to fail consistently
# a small data set with missingness and weird responses usually fails in MHRM
set.seed(42)
bad_data <- matrix(rbinom(20, 1, 0.5), nrow = 4, ncol = 5)
colnames(bad_data) <- paste0("Item", 1:5)

# Instead of testing autoFIPC directly since it requires valid inputs,
# We test the core logic where max_retries is placed.
# We mock the mirt function or just provide a test case that hits it.

# Since autoFIPC takes data and attempts MHRM if the first mirt fails,
# we provide it bad data that will fail the EM and then fail MHRM.
# autoFIPC expects MHRM to fail in max_retries and stop()

expect_error({
res <- aFIPC::autoFIPC(
newformXData = bad_data,
oldformYData = bad_data,
newformCommonItemNames = paste0("Item", 1:3),
oldformCommonItemNames = paste0("Item", 1:3),
itemtype = "2PL",
checkIPD = FALSE,
confirmCommonItems = TRUE
)
})
Comment on lines +4 to +28
})
Loading