Locking / RCU · Linux v6.6 · source + diagram note

atomic/refcount

Linux v6.6의 refcount_t: 살아 있는 객체의 참조 획득 조건, 마지막 put, 실패 시 포화 동작, 메모리 순서를 고정 소스로 읽습니다.

전체 흐름

원자적 카운터 변경은 lock 획득·해제와 다릅니다. 이미 참조를 가진 소유자는 refcount_inc()를 쓸 수 있지만, lookup에서 얻은 후보는 별도의 lock/RCU 등으로 객체 메모리를 안정화한 뒤 refcount_inc_not_zero()의 성공을 검사해야 합니다. 참조 획득만으로 포인터 공개나 임의의 필드 접근 순서가 자동 보장되지는 않습니다.

그림 1. 기존 소유자의 증가와 lookup 후보의 조건부 참조 획득
이미 가진 참조
기존 소유자객체의 유효 참조를 이미 보유
refcount_inc()증가 자체는 relaxed
새 참조별도 소유권을 갖고 사용
lookup 후 획득
안정된 저장소lock·RCU 등으로 메모리 안정성 확보
refcount_inc_not_zero()0이면 false; 성공 여부 검사
새 참조성공했을 때만 사용

두 줄은 대안 경로입니다. 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 발췌

보존된 발췌: include/linux/refcount.h, 122–160행
	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을 설명하는 파일 머리말 주석

원본 주석: include/linux/refcount.h, 24–63행
 *                                     <---------- 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되는지 호출자 코드에서 따로 확인해야 합니다.

표 1. refcount API의 실제 역할과 순서
API성공·상태 변화메모리 순서와 오해 방지
refcount_inc()기존 유효 참조를 가진 호출자의 증가; 0에서 호출하면 경고·포화 가능증가 자체는 relaxed; acquire나 포인터 publish 아님
refcount_inc_not_zero()0이면 실패; 성공했을 때만 참조 획득일반형은 full acquire 아님; 성공한 control dependency가 뒤따르는 store를 제한
refcount_dec_and_test()release 감소; 1→0이면 truetrue일 때 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에 있습니다.

표 2. 한 줄의 호출 체인이 아닌 역할 구분
대상역할경계
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은 객체를 해제하지 않습니다.

그림 2. refcount_dec_and_test()의 대표 결과
dec_and_test()release 감소 후 결과 판정
false참조가 남아 있으면 이 호출자는 free하지 않음
true: 1→0성공 시 acquire; 호출자 소멸 경로에서 정리
잘못된 값·포화WARN·saturate 등 예외 처리; 정상 free 경로 아님

상자는 조건별 갈림길입니다. 1→0의 true가 반환되더라도 구체적인 해제 함수나 RCU 지연 회수 여부는 이 header가 아니라 호출자가 정합니다. 고정 원문 dec_and_test.

표 3. 참조 수명과 공개 순서를 분리해서 읽기
장면필요한 확인잘못된 단정
후보 pointer를 찾음lock/RCU 등으로 refcount 접근까지 메모리 안정성 확보pointer가 보이면 살아 있는 참조도 자동 보유
참조 수 0inc_not_zero() 실패를 검사하고 사용 중지0→1로 되살려도 안전
마지막 putdec_and_test() true 여부와 호출자 destructor모든 decrement가 즉시 free
경고·포화overflow/underflow 원인과 누수 가능성 점검포화 값이 정상적인 마지막 0

이 버전의 소스에서는 __refcount_sub_and_test()는 old == i로 마지막 감소를 판정합니다. 정상적인 1→0 소유권 판단은 같지만, 잘못된 값에 대한 방어 조건은 버전별 원문 그대로 읽어야 합니다.

불변 조건과 버전별 예외

0에서 획득 금지

lookup 후보에는 refcount_inc_not_zero()의 성공을 확인합니다. 단순 refcount_inc()는 이미 유효한 참조를 가진 호출자를 전제합니다.

순서 범위

일반 increment는 relaxed, decrement는 release, 성공한 dec_and_test()는 acquire입니다. refcount만으로 pointer publish나 모든 필드 가시성을 증명하지 않습니다.

포화 시 해제 금지

overflow·underflow 보호는 REFCOUNT_SATURATED로 누수를 택할 수 있습니다. 경고를 정상적인 마지막 put의 증거로 사용하지 않습니다.

이 버전의 특수 API

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 연산의 모든 호출을 일반 함수 추적으로 볼 수 있다는 전제는 피합니다.

표 4. 각 도구가 확인하는 문제
도구·관찰확인할 가설한계
refcount saturation WARN0에서의 증가, 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와 호출자 코드에 어떻게 구현되었는가?