引用: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代码当中:
引用: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):
引用: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来完成的:
引用: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函数引用出错。
我本想试着修正这个问题,不过现在看来牵扯比较大,只能暂时作罢。