전체 흐름
원자적 카운터 변경은 lock 획득·해제와 다릅니다. 이미 참조를 가진 소유자는 refcount_inc()를 쓸 수 있지만, lookup에서 얻은 후보는 별도의 lock/RCU 등으로 객체 메모리를 안정화한 뒤 refcount_inc_not_zero()의 성공을 검사해야 합니다. 참조 획득만으로 포인터 공개나 임의의 필드 접근 순서가 자동 보장되지는 않습니다.
두 줄은 대안 경로입니다. refcount_warn_saturate()는 정상 경로의 첫 단계가 아니라 잘못된 증가·감소나 overflow를 감지했을 때의 경고·포화 처리입니다. 고정 원문 참조 증가.
소스 코드 위치
먼저 확인할 Linux v6.6 소스 파일
Linux v6.6: include/linux/atomic/atomic-instrumented.h
Linux v6.6: include/linux/refcount.h
Linux v6.6: lib/refcount.c
설명: 주요 API와 순서 계약은 include/linux/refcount.h의 주석과 inline 함수에서 확인합니다. lib/refcount.c의 경고·포화 구현은 예외 경로입니다. refcount_t의 타입 정의는 같은 refcount.h에 있습니다. 위 파일 목록 원문은 보존했습니다.
대표 코드
refcount.h의 선언·set/read·add-not-zero 발췌
REFCOUNT_ADD_UAF,
REFCOUNT_SUB_UAF,
REFCOUNT_DEC_LEAK,
};
void refcount_warn_saturate(refcount_t *r, enum refcount_saturation_type t);
/**
* refcount_set - set a refcount's value
* @r: the refcount
* @n: value to which the refcount will be set
*/
static inline void refcount_set(refcount_t *r, int n)
{
atomic_set(&r->refs, n);
}
/**
* refcount_read - get a refcount's value
* @r: the refcount
*
* Return: the refcount's value
*/
static inline unsigned int refcount_read(const refcount_t *r)
{
return atomic_read(&r->refs);
}
static inline __must_check bool __refcount_add_not_zero(int i, refcount_t *r, int *oldp)
{
int old = refcount_read(r);
do {
if (!old)
break;
} while (!atomic_try_cmpxchg_relaxed(&r->refs, &old, old + i));
if (oldp)
*oldp = old;
읽는 법: 첫 코드 조각은 refcount_warn_saturate()의 구현이 아니라 enum 끝, 선언, set/read, __refcount_add_not_zero() 앞부분을 연속으로 발췌한 것입니다. refcount_inc() 본문이나 lock 획득은 이 조각에 없습니다. 실제 v6.6 set/read · add-not-zero.
포화 위험과 memory ordering을 설명하는 파일 머리말 주석
* <---------- bad value! ---------->
*
* (in a signed view of the world, the "bad value" range corresponds to
* a negative counter value).
*
* As an example, consider a refcount_inc() operation that causes the counter
* to overflow:
*
* int old = atomic_fetch_add_relaxed(r);
* // old is INT_MAX, refcount now INT_MIN (0x8000_0000)
* if (old < 0)
* atomic_set(r, REFCOUNT_SATURATED);
*
* If another thread also performs a refcount_inc() operation between the two
* atomic operations, then the count will continue to edge closer to 0. If it
* reaches a value of 1 before /any/ of the threads reset it to the saturated
* value, then a concurrent refcount_dec_and_test() may erroneously free the
* underlying object.
* Linux limits the maximum number of tasks to PID_MAX_LIMIT, which is currently
* 0x400000 (and can't easily be raised in the future beyond FUTEX_TID_MASK).
* With the current PID limit, if no batched refcounting operations are used and
* the attacker can't repeatedly trigger kernel oopses in the middle of refcount
* operations, this makes it impossible for a saturated refcount to leave the
* saturation range, even if it is possible for multiple uses of the same
* refcount to nest in the context of a single task:
*
* (UINT_MAX+1-REFCOUNT_SATURATED) / PID_MAX_LIMIT =
* 0x40000000 / 0x400000 = 0x100 = 256
*
* If hundreds of references are added/removed with a single refcounting
* operation, it may potentially be possible to leave the saturation range; but
* given the precise timing details involved with the round-robin scheduling of
* each thread manipulating the refcount and the need to hit the race multiple
* times in succession, there doesn't appear to be a practical avenue of attack
* even if using refcount_add() operations with larger increments.
*
* Memory ordering
* ===============
*
* Memory ordering rules are slightly relaxed wrt regular atomic_t functions
읽는 법: 이 발췌는 refcount_inc() 본문이 아니라 overflow 위험과 메모리 순서 주석의 일부입니다. 실제 함수는 고정 원문에서 확인합니다. 주석의 예시 atomic_fetch_add_relaxed()는 refcount_inc()를 lock으로 만드는 코드가 아닙니다.
함수별 분석
refcount_t API는 객체 수명에 맞춘 원자 카운터이며 임계 구역을 잠그는 API가 아닙니다. 일반 증가는 relaxed이고, 감소는 release이며, dec_and_test()가 마지막 1→0을 확인한 경우에만 추가 acquire가 있습니다. 어떤 lock/RCU가 lookup 중 객체 메모리를 안정화하는지, 포인터는 어떤 규칙으로 publish되는지 호출자 코드에서 따로 확인해야 합니다.
| API | 성공·상태 변화 | 메모리 순서와 오해 방지 |
|---|---|---|
refcount_inc() | 기존 유효 참조를 가진 호출자의 증가; 0에서 호출하면 경고·포화 가능 | 증가 자체는 relaxed; acquire나 포인터 publish 아님 |
refcount_inc_not_zero() | 0이면 실패; 성공했을 때만 참조 획득 | 일반형은 full acquire 아님; 성공한 control dependency가 뒤따르는 store를 제한 |
refcount_dec_and_test() | release 감소; 1→0이면 true | true일 때 acquire가 뒤따르는 소멸자보다 앞선 접근을 정렬; free는 호출자 책임 |
refcount_warn_saturate() | 오버플로·잘못된 감소 등에서 경고와 포화 처리 | 정상 참조 획득의 필수 선행 호출이 아님 |
고정 원문의 memory-ordering 계약 · 마지막 put 구현. refcount_inc_not_zero()는 relaxed이며 성공 시 향후 store에 대한 control dependency를 제공합니다. 이 버전의 refcount.h에는 재사용 객체 전용 refcount_inc_not_zero_acquire()와 refcount_set_release()가 없습니다.
구조체와 필드
atomic_t → refcount_t → READ_ONCE/WRITE_ONCE → barrier는 실행 순서가 아닙니다. refcount_t는 내부 카운터에 atomic 연산을 사용하지만, 포인터의 생존성과 데이터의 가시성까지 한 번에 보장하지 않습니다. refcount_t의 타입 정의는 같은 refcount.h에 있습니다.
| 대상 | 역할 | 경계 |
|---|---|---|
refcount_t | 포화 성질을 가진 객체 수명 카운터 | 내부 atomic_t refs가 카운터 값; 임계 구역 lock은 아님 |
atomic_t | 범용 원자 정수 연산 | refcount용 overflow·underflow 보호 의미와 동일하게 취급하지 않음 |
| 포인터·객체 필드 | 객체 발견, publish, 초기화된 데이터 접근 | 참조 획득과 별도로 lookup 안정성·publish 순서 필요 |
READ_ONCE/WRITE_ONCE·barrier | 선택한 접근과 메모리 순서의 도구 | 카운터 변경 뒤에 자동으로 실행되는 단계가 아님 |
마지막 put의 갈림길
“increment acquire → publish store → decrement release → free”는 refcount.h에 정의된 공통 상태 기계가 아닙니다. 일반 refcount_inc()는 acquire가 아니고, 포인터 publish는 별도 규칙이며, 마지막 감소가 아닌 put은 객체를 해제하지 않습니다.
refcount_dec_and_test()의 대표 결과상자는 조건별 갈림길입니다. 1→0의 true가 반환되더라도 구체적인 해제 함수나 RCU 지연 회수 여부는 이 header가 아니라 호출자가 정합니다. 고정 원문 dec_and_test.
| 장면 | 필요한 확인 | 잘못된 단정 |
|---|---|---|
| 후보 pointer를 찾음 | lock/RCU 등으로 refcount 접근까지 메모리 안정성 확보 | pointer가 보이면 살아 있는 참조도 자동 보유 |
| 참조 수 0 | inc_not_zero() 실패를 검사하고 사용 중지 | 0→1로 되살려도 안전 |
| 마지막 put | dec_and_test() true 여부와 호출자 destructor | 모든 decrement가 즉시 free |
| 경고·포화 | overflow/underflow 원인과 누수 가능성 점검 | 포화 값이 정상적인 마지막 0 |
이 버전의 소스에서는 __refcount_sub_and_test()는 old == i로 마지막 감소를 판정합니다. 정상적인 1→0 소유권 판단은 같지만, 잘못된 값에 대한 방어 조건은 버전별 원문 그대로 읽어야 합니다.
불변 조건과 버전별 예외
lookup 후보에는 refcount_inc_not_zero()의 성공을 확인합니다. 단순 refcount_inc()는 이미 유효한 참조를 가진 호출자를 전제합니다.
일반 increment는 relaxed, decrement는 release, 성공한 dec_and_test()는 acquire입니다. refcount만으로 pointer publish나 모든 필드 가시성을 증명하지 않습니다.
overflow·underflow 보호는 REFCOUNT_SATURATED로 누수를 택할 수 있습니다. 경고를 정상적인 마지막 put의 증거로 사용하지 않습니다.
refcount_inc_not_zero()는 relaxed이며 성공 시 향후 store에 대한 control dependency를 제공합니다. 이 버전의 refcount.h에는 재사용 객체 전용 refcount_inc_not_zero_acquire()와 refcount_set_release()가 없습니다.
고정 원문 memory-ordering 설명. 객체 소멸 함수, lock/RCU 보호 범위, 포인터 publish 규칙은 실제 호출자의 코드에서 별도로 확인해야 합니다.
계측과 검증
먼저 수명 오류인지, 데이터 경쟁인지, 메모리 순서 오류인지 가설을 구분합니다. inline refcount 연산의 모든 호출을 일반 함수 추적으로 볼 수 있다는 전제는 피합니다.
| 도구·관찰 | 확인할 가설 | 한계 |
|---|---|---|
| refcount saturation WARN | 0에서의 증가, overflow/underflow 또는 잘못된 마지막 감소 | 경고 없음만으로 수명 안전을 증명하지 않음 |
| KASAN / KCSAN | 해제 후 접근 또는 데이터 경쟁 징후 | 선택한 ordering 계약 전체를 증명하지 않음 |
| LKMM litmus | 선택한 acquire/release·control dependency 배치의 허용 결과 | 특정 제품 코드의 실제 실행 trace는 아님 |
| 함수 추적(가능한 빌드) | refcount_warn_saturate() 예외 경로 호출 | 정상 inline refcount_inc() 전체의 이벤트 목록은 아님 |
실험 전 해당 빌드의 KASAN/KCSAN 설정과 함수 추적 가능 여부를 확인합니다. 이전의 무관한 :mod:* 추적 명령은 제거했습니다. 기본 순서 계약은 v6.6 원문 주석에 있습니다.
추가 확인 사항
- lookup에서 얻은 후보의 저장소는 refcount를 올릴 때까지 무엇이 안정화하는가?
- 호출자가 이미 참조를 갖는가, 아니면
inc_not_zero()의 성공을 검사해야 하는가? - 마지막 1→0을 확인한 호출자만 정리하는가? 실제 free/RCU 지연 경로는 어디인가?
- pointer publish와 객체 재사용에 필요한 acquire/release가 이 버전의 API와 호출자 코드에 어떻게 구현되었는가?