Skip to content

[CBRD-27229] [Backport-10.2.19] Fix SIGFPE (server crash / SA infinite loop) on MIN / -1 in integer and NUMERIC arithmetic - #7692

Merged
jongmin-won merged 4 commits into
CUBRID:release/10.2from
jongmin-won:backport_10.2_CBRD-27229
Aug 20, 2026
Merged

[CBRD-27229] [Backport-10.2.19] Fix SIGFPE (server crash / SA infinite loop) on MIN / -1 in integer and NUMERIC arithmetic#7692
jongmin-won merged 4 commits into
CUBRID:release/10.2from
jongmin-won:backport_10.2_CBRD-27229

Conversation

@jongmin-won

Copy link
Copy Markdown
Contributor

http://jira.cubrid.org/browse/CBRD-27229

Purpose

정수 타입의 최솟값을 -1로 나누면(INT_MIN / -1, BIGINT_MIN / -1) 몫이 해당 타입으로 표현 불가능해 machine divide 명령에서 하드웨어 divide 예외(SIGFPE)가 발생합니다.
CS 모드에서는 cub_server가 비정상 종료되고, SA 모드에서는 폴트가 발생한 명령을 반복 실행하며 무한 루프에 빠집니다.

결함은 세 가지 형태로 존재합니다.

  • a / b, a % b, MOD(a, b): MIN / -1 오버플로 검사 없이 machine divide를 실행
  • a * b: 검사 매크로 OR_CHECK_MULT_OVERFLOW 자체가 나눗셈((c) / (b))을 수행하므로, INT_MIN * -1 검증 도중 매크로 내부에서 트랩 발생
  • NUMERIC: numeric_div()가 양쪽 피연산자가 int/BIGINT에 들어가면 machine divide 빠른 경로를 타므로, INT_MIN·BIGINT_MIN에 해당하는 NUMERIC 값에서 트랩 발생

SHORT는 정수 승격 덕분에 트랩은 없지만, -32768 / -1-32768로 잘려 반환되는 값 오류가 있었습니다.

Implementation

  • object_representation.h: OR_CHECK_MULT_OVERFLOWb == -1 케이스를 나눗셈 없이 판정하도록 수정.
    • 매크로는 (wrap된) 곱셈 이후 실행되므로, b == -1일 때 c == a가 성립하는 경우는 a == 0(정상)과 a == MIN(오버플로)뿐이라 (a) != 0 && (c) == (a)로 충분합니다.
    • 남은 (c) / (b)b != 0, -1일 때만 실행되어 트랩 조건이 없습니다.
    • 호출처 6곳이 일괄 해결됩니다. (__builtin_mul_overflow_p와 4,800만 케이스 대조, 불일치 0건)
  • query_opfunc.c
    • qdata_divide_short/int/bigint(): 나눗셈 전에 OR_CHECK_*_DIV_OVERFLOW 검사 후 ER_QPROC_OVERFLOW_DIVISION 반환
    • qdata_divmod_dbval(): a % b / a MOD b 경로에서 MIN % -1은 트랩 대신 0을 반환.
    • 피연산자가 이미 DB_BIGINT로 확장되어 있어 실제로 트랩 가능한 것은 BIGINT 케이스뿐입니다.
    • 아울러 DIV 오버플로 에러를 ER_QPROC_OVERFLOW_ADDITION에서 ER_QPROC_OVERFLOW_DIVISION으로 교체
  • arithmetic.c: db_mod_int() / db_mod_bigint()(함수형 MOD(a, b), 원래 피연산자 폭으로 계산)의 divisor 타입별 분기에서 MIN % -1 → 0 반환
  • type_checking.c (클라이언트 상수 폴딩): PT_DIVIDE는 MIN / -1을 오버플로 에러로, PT_MOD는 MIN % -1을 0으로 폴딩하여 서버 동작과 일치시킴
  • numeric_opfunc.c (numeric_div()): int 빠른 경로가 트랩 조건이면 피연산자를 DB_BIGINT로 넓혀 나눗셈, BIGINT 빠른 경로가 트랩 조건이면 numeric_long_div()로 우회.
    • 두 경우 모두 몫과 나머지를 정확히 산출하며 — NUMERIC(38,0)으로 표현 가능하므로 — 에러 없이 정답을 반환합니다.

Remarks

  • a % b는 MOD 토큰으로 렉싱되어 a MOD b와 동일하게 qdata_divmod_dbval()에서 DB_BIGINT 폭으로 계산되므로 INTEGER 피연산자는 원래도 안전했습니다.
  • 반면 함수형 MOD(a, b)는 원래 폭으로 계산하므로 INTEGER에서도 트랩이 발생했습니다.
  • MIN % -1은 수학적으로 0이므로 에러 없이 0을 반환하며, 이는 MOD 경로의 기존 divisor == 0 처리 방식과 일관됩니다.

…e loop) on MIN / -1 in integer and NUMERIC arithmetic

http://jira.cubrid.org/browse/CBRD-27229

Dividing the minimum value of an integer type by -1 (`INT_MIN / -1`, `BIGINT_MIN / -1`) yields a quotient that is not representable in that type, so the machine divide instruction raises a hardware divide exception (SIGFPE). In CS mode `cub_server` terminates abnormally; in SA mode the faulting instruction is re-executed forever, hanging the process.

* `object_representation.h`: make `OR_CHECK_MULT_OVERFLOW` decide the `b == -1` case without a division, so `INT_MIN * -1` no longer traps inside the check macro itself. Since the macro runs after the (wrapped) multiplication, `c == a` with `b == -1` holds only for `a == 0` (fine) and `a == MIN` (overflow), so `(a) != 0 && (c) == (a)` is sufficient. The remaining `(c) / (b)` now runs only when `b != 0, -1`. Fixes all 6 call sites at once.
* `query_opfunc.c`: check `OR_CHECK_*_DIV_OVERFLOW` before the divide in `qdata_divide_short/int/bigint()` and return `ER_QPROC_OVERFLOW_DIVISION`. In `qdata_divmod_dbval()` return 0 for `MIN % -1` on the `a % b` / `a MOD b` path instead of trapping. Also replace `ER_QPROC_OVERFLOW_ADDITION` with `ER_QPROC_OVERFLOW_DIVISION` for DIV overflow.
* `arithmetic.c`: return 0 for `MIN % -1` in each divisor-type branch of `db_mod_int()` / `db_mod_bigint()` (functional `MOD(a, b)`, which computes at the original operand width and therefore trapped even for INTEGER).
* `type_checking.c`: match the server behaviour during client-side constant folding — fold `MIN / -1` to an overflow error for `PT_DIVIDE`, and `MIN % -1` to 0 for `PT_MOD`.
* `numeric_opfunc.c`: in `numeric_div()`, widen the operands to `DB_BIGINT` when the int fast path would trap, and fall back to `numeric_long_div()` when the BIGINT fast path would trap. Both produce the exact quotient and remainder, which are representable as NUMERIC(38,0), so no error is raised.

`MIN % -1` is mathematically 0, so it returns 0 without an error, consistent with the existing `divisor == 0` handling on the MOD path.

Clean cherry-pick of the 11.0 backport 8cdb2fc, via the 11.3 backport 7a57a2d and the 11.4 backport 816c712 (CUBRID#7689), originally CUBRID#7687.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jongmin-won jongmin-won self-assigned this Aug 13, 2026
@jongmin-won
jongmin-won requested a review from beyondykk9 August 13, 2026 02:21
@jongmin-won
jongmin-won marked this pull request as ready for review August 13, 2026 02:29
@greptile-apps

greptile-apps Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Reviews (1): Last reviewed commit: "[CBRD-27229] [Backport-10.2.19] Fix SIGF..." | Re-trigger Greptile

jongmin-won and others added 3 commits August 19, 2026 16:44
…t dividing

http://jira.cubrid.org/browse/CBRD-27255

정수 산술의 SIGFPE 문제를 함께 내보내기 위해 CBRD-27229 백포트와 같은 브랜치에 묶어 반영한다.

`OR_CHECK_MULT_OVERFLOW ()` 는 이미 래핑된 곱을 나누어 오버플로를 판정한다. 그래서 `INT_MIN * -1` 을 검사하면 `INT_MIN / -1` 이 실행되고, 몫 2147483648 이 int 범위를 벗어나 하드웨어 나눗셈 예외가 발생했다. 오버플로를 검사하려던 코드가 오버플로로 죽는 셈이다. 앞선 CBRD-27229 커밋이 `b == -1` 을 나눗셈 없이 판정하도록 바꿔 트랩 자체는 이미 막았고, 이 커밋은 곱셈 경로에서 나눗셈을 아예 없앤다.

* `query_opfunc.c`: `qdata_multiply_short ()` / `qdata_multiply_int ()` / `qdata_multiply_bigint ()` 가 `__builtin_mul_overflow ()` 로 판정한다. 나눗셈 대신 하드웨어 오버플로 플래그를 읽으므로 트랩이 발생할 여지가 없다.
* `type_checking.c`: `pt_evaluate_db_value_expr ()` 의 SHORT / INTEGER / BIGINT 상수 폴딩 분기도 동일하게 바꾼다.

검사를 다시 곱셈으로 되돌리지 못하게 막으려고 두었던 `volatile` 지역 변수는 나눗셈과 함께 사라진다. 피연산자가 컴파일 시점 상수가 아니므로 나눗셈이 없어진 뒤에는 필요하지 않다.

오버플로 판정 결과는 기존 검사가 살아남던 모든 입력에서 같다. 래핑된 곱 `w = a * b - k * 2^N` (k != 0) 은 `|k * 2^N|` 이 나눗셈이 흡수할 수 있는 어떤 `|b|` 보다 크므로 `w / b == a` 를 만족할 수 없고, 범위에 들어간 곱은 정확히 나누어떨어진다. 이 브랜치에 들어있는 매크로와 직접 비교해 SHORT 준전수 스윕 (피승수 전수 × 승수 37 간격), INTEGER 경계값 전조합, INTEGER · BIGINT 무작위 각 400 만 쌍, 총 1 억 2413 만 건에서 판정과 결과값 불일치 0 건을 확인했다.

부수적으로 곱셈마다 하드웨어 나눗셈 1 회와 volatile 로 인한 스택 왕복이 사라진다.

`__builtin_mul_overflow ()` 는 GCC 5.0 이상에서 제공된다. 10.2 의 CMakeLists 는 GCC 4.4.7 이상을 허용하므로, 4.x 툴체인으로 빌드하는 환경이 남아 있다면 별도 대응이 필요하다. 이 커밋은 GCC 8.5.0 debug 빌드로 확인했다.

CUBRID#7722 수동 백포트. 11.4 1e92dde / 11.3 4b2be8c / 11.0 e321af9 와 내용이 같고, 대상 함수들이 develop 과 동일해 변형 없이 적용된다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…k portable to MSVC

http://jira.cubrid.org/browse/CBRD-27255

`__builtin_mul_overflow ()` 는 GCC / clang 확장이라 MSVC 에서 `error C3861: '__builtin_mul_overflow': identifier not found` 로 컴파일이 실패한다. 11.4 의 CircleCI 에는 Windows 잡 (`build-windows`, windows-server-2019, MSVC 2017) 이 있어 실제로 이 에러로 빌드가 깨졌다. 10.2 / 11.0 / 11.3 의 CI 에는 Windows 잡이 없어 드러나지 않았을 뿐, 코드 이식성은 동일하게 깨진 상태였다.

`object_representation.h` 에 `OR_MULT_OVERFLOW ()` 를 두고 컴파일러별로 갈라 놓는다.

* GCC / clang: `__builtin_mul_overflow ()` 를 그대로 사용한다. 하드웨어 오버플로 플래그로 판정하므로 나눗셈이 없다.
* 그 외 (MSVC): 곱한 뒤 `OR_CHECK_MULT_OVERFLOW ()` 로 판정한다. 이 매크로는 CBRD-27229 에서 `b == -1` 을 나눗셈 없이 판정하도록 고쳤으므로 이 경로에도 트랩 조건이 없다.

호출부 6 곳 (`qdata_multiply_short ()` / `qdata_multiply_int ()` / `qdata_multiply_bigint ()`, `pt_evaluate_db_value_expr ()` 의 SHORT / INTEGER / BIGINT 상수 폴딩) 은 매크로 이름만 바뀐다.

두 경로의 판정과 결과값은 동일하다. MSVC 는 `.c` 를 C++ 로 컴파일하므로 C 와 C++ 양쪽으로 대조했고, SHORT 준전수 스윕 (피승수 전수 × 승수 37 간격), INTEGER 경계값 전조합, INTEGER · BIGINT 무작위 각 400 만 쌍, 총 1 억 2413 만 건에서 불일치 0 건이다. GCC 8.5.0 debug 빌드로 확인했다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jongmin-won
jongmin-won merged commit 9f72232 into CUBRID:release/10.2 Aug 20, 2026
3 checks passed
@jongmin-won
jongmin-won deleted the backport_10.2_CBRD-27229 branch August 20, 2026 03:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants