crypto: user - Fix crypto_alg_match race

The function crypto_alg_match returns an algorithm without taking
any references on it.  This means that the algorithm can be freed
at any time, therefore all users of crypto_alg_match are buggy.

This patch fixes this by taking a reference count on the algorithm
to prevent such races.

Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
This commit is contained in:
Herbert Xu 2015-04-07 21:27:01 +08:00
parent 9cd223239a
commit 016baaa118
1 changed files with 29 additions and 10 deletions

View File

@ -62,11 +62,15 @@ static struct crypto_alg *crypto_alg_match(struct crypto_user_alg *p, int exact)
else if (!exact) else if (!exact)
match = !strcmp(q->cra_name, p->cru_name); match = !strcmp(q->cra_name, p->cru_name);
if (match) { if (!match)
continue;
if (unlikely(!crypto_mod_get(q)))
continue;
alg = q; alg = q;
break; break;
} }
}
up_read(&crypto_alg_sem); up_read(&crypto_alg_sem);
@ -205,9 +209,10 @@ static int crypto_report(struct sk_buff *in_skb, struct nlmsghdr *in_nlh,
if (!alg) if (!alg)
return -ENOENT; return -ENOENT;
err = -ENOMEM;
skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC); skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC);
if (!skb) if (!skb)
return -ENOMEM; goto drop_alg;
info.in_skb = in_skb; info.in_skb = in_skb;
info.out_skb = skb; info.out_skb = skb;
@ -215,6 +220,10 @@ static int crypto_report(struct sk_buff *in_skb, struct nlmsghdr *in_nlh,
info.nlmsg_flags = 0; info.nlmsg_flags = 0;
err = crypto_report_alg(alg, &info); err = crypto_report_alg(alg, &info);
drop_alg:
crypto_mod_put(alg);
if (err) if (err)
return err; return err;
@ -284,6 +293,7 @@ static int crypto_update_alg(struct sk_buff *skb, struct nlmsghdr *nlh,
up_write(&crypto_alg_sem); up_write(&crypto_alg_sem);
crypto_mod_put(alg);
crypto_remove_final(&list); crypto_remove_final(&list);
return 0; return 0;
@ -294,6 +304,7 @@ static int crypto_del_alg(struct sk_buff *skb, struct nlmsghdr *nlh,
{ {
struct crypto_alg *alg; struct crypto_alg *alg;
struct crypto_user_alg *p = nlmsg_data(nlh); struct crypto_user_alg *p = nlmsg_data(nlh);
int err;
if (!netlink_capable(skb, CAP_NET_ADMIN)) if (!netlink_capable(skb, CAP_NET_ADMIN))
return -EPERM; return -EPERM;
@ -310,13 +321,19 @@ static int crypto_del_alg(struct sk_buff *skb, struct nlmsghdr *nlh,
* if we try to unregister. Unregistering such an algorithm without * if we try to unregister. Unregistering such an algorithm without
* removing the module is not possible, so we restrict to crypto * removing the module is not possible, so we restrict to crypto
* instances that are build from templates. */ * instances that are build from templates. */
err = -EINVAL;
if (!(alg->cra_flags & CRYPTO_ALG_INSTANCE)) if (!(alg->cra_flags & CRYPTO_ALG_INSTANCE))
return -EINVAL; goto drop_alg;
if (atomic_read(&alg->cra_refcnt) != 1) err = -EBUSY;
return -EBUSY; if (atomic_read(&alg->cra_refcnt) > 2)
goto drop_alg;
return crypto_unregister_instance((struct crypto_instance *)alg); err = crypto_unregister_instance((struct crypto_instance *)alg);
drop_alg:
crypto_mod_put(alg);
return err;
} }
static struct crypto_alg *crypto_user_skcipher_alg(const char *name, u32 type, static struct crypto_alg *crypto_user_skcipher_alg(const char *name, u32 type,
@ -395,8 +412,10 @@ static int crypto_add_alg(struct sk_buff *skb, struct nlmsghdr *nlh,
return -EINVAL; return -EINVAL;
alg = crypto_alg_match(p, exact); alg = crypto_alg_match(p, exact);
if (alg) if (alg) {
crypto_mod_put(alg);
return -EEXIST; return -EEXIST;
}
if (strlen(p->cru_driver_name)) if (strlen(p->cru_driver_name))
name = p->cru_driver_name; name = p->cru_driver_name;