歡迎您光臨本站 註冊首頁

在項目開發過程當中,發現CentOS 5當中,若對rpcsec_gss_krb5模塊載入、卸載后再載入將導致kernel panic。通過對panic后Oops信息的分析可知panic位置處於sunrpc.ko模塊auth_domain_put函數。這一函數在多個模塊當中被調用,應該不至於出現嚴重問題,遂分析調用者auth_rpcgss模塊中的svcauth_gss_register_pseudoflavor函數:
QUOTE:
int
svcauth_gss_register_pseudoflavor(u32 pseudoflavor, char * name)
{
struct gss_domain *new;
struct auth_domain *test;
int stat = -ENOMEM;

new = kmalloc(sizeof(*new), GFP_KERNEL);
if (!new)
goto out;
kref_init(&new->h.ref);
new->h.name = kmalloc(strlen(name) + 1, GFP_KERNEL);
if (!new->h.name)
goto out_free_dom;
strcpy(new->h.name, name);
new->h.flavour = &svcauthops_gss;
new->pseudoflavor = pseudoflavor;

test = auth_domain_lookup(name, &new->h);
if (test != &new->h) { /* XXX Duplicate registration? */
auth_domain_put(&new->h); // <<<<---------- kernel panic here
/* dangling ref-count... */
goto out;
}
return 0;

out_free_dom:
kfree(new);
out:
return stat;
}

注意到幾個特徵:
1、rpcsec_gss_krb5模塊是在再載入的過程當中才發生kernel panic的,於是這個panic可能與卸載模塊工作不到位有關

2、panic的位置,處於auth_domain_lookup之後,從代碼上下文和註釋當中都可以看到調用auth_domain_put是為了避免重複註冊,而對后註冊的信息進行刪除

查看代碼有這樣的發現,首先第一次載入模塊時,auth_domain_lookup通過對name進行哈希運算后發現new->h當中的地址並未在哈希表當中出現,於是將其插入表中,並將傳入的new->h值返回。若第二次調用auth_domain_lookup使用相同的名字,不同的new->h值時,將返回哈希表中的值,而不是傳入的new->h值,所以後續代碼可以通過此檢測是否有重複name的註冊。不幸的是,rpcsec_gss_krb5模塊在載入的時候註冊了若干的name,但是卻沒有在卸載的時候從哈希表當中刪除對應的表項,使得第二次載入模塊時會發現有重複的表項出現。當然,這只是kernel panic的誘因,而不是直接原因。

直接導致kernel panic的原因在auth_domain_put代碼當中:
QUOTE:
void auth_domain_put(struct auth_domain *dom)
{
if (atomic_dec_and_lock(&dom->ref.refcount, &auth_domain_lock)) {
hlist_del(&dom->hash); // <<<----------- looking here
dom->flavour->domain_release(dom);
}
}

其在對傳入的new->h信息進行了hlist_del操作,而hlist_del的函數如下,並調用了__hlist_del(n):
QUOTE:
static inline void hlist_del(struct hlist_node *n)
{
__hlist_del(n); // <<-------- pay attention
n->next = LIST_POISON1;
n->pprev = LIST_POISON2;
}

static inline void __hlist_del(struct hlist_node *n)
{
struct hlist_node *next = n->next;
struct hlist_node **pprev = n->pprev; // <<--------- pprev get a wrong value
*pprev = next; // <<-------- use this wrong pointer will cause unknow result
if (next)
next->pprev = pprev;
}

在解釋上面代碼之前,請回頭看看svcauth_gss_register_pseudoflavor函數的代碼,其對new->h的操作前後是先通過kmalloc為new分配了空間,而對new->h當中的結構體內容內部賦值了name,flavour,而對ref和hash沒有進行操作,其希望是通過函數auth_domain_lookup來完成的:
QUOTE:
struct auth_domain *
auth_domain_lookup(char *name, struct auth_domain *new)
{
struct auth_domain *hp;
struct hlist_head *head;
struct hlist_node *np;

head = &auth_domain_table[hash_str(name, DN_HASHBITS)];

spin_lock(&auth_domain_lock);

hlist_for_each_entry(hp, np, head, hash) {
if (strcmp(hp->name, name)==0) {
kref_get(&hp->ref);
spin_unlock(&auth_domain_lock);
return hp; // <<---------- if name is duplicated, no operation to the new parameter
}
}
if (new) { // <<---------- if name is fresh for the list, run here
hlist_add_head(&new->hash, head);
kref_get(&new->ref);
}
spin_unlock(&auth_domain_lock);
return new;
}

當然,很不幸的是,auth_domain_lookup函數只有在當name沒有註冊的時候才會對ref和hash成員進行賦值,而當有重複的name時,後傳入的auth_domain結構完全沒有被改變。於是在svcauth_gss_register_pseudoflavor函數當中,調用auth_domain_put時所操作的auth_domain結構體中的ref和hash成員都是kmalloc后未初始化的值,導致__hlist_del函數引用出錯。

我本想試著修正這個問題,不過現在看來牽扯比較大,只能暫時作罷。

[火星人 ] CentOS 5 rpcsec_gss_krb5模塊卸載再重載入后導致kernel panic原因分析已經有717次圍觀

http://coctec.com/docs/linux/show-post-186946.html