/* Keyring handling * * Copyright (C) 2004-2005, 2008 Red Hat, Inc. All Rights Reserved. * Written by David Howells (dhowells@redhat.com) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. */ #include <linux/module.h> #include <linux/init.h> #include <linux/sched.h> #include <linux/slab.h> #include <linux/security.h> #include <linux/seq_file.h> #include <linux/err.h> #include <keys/keyring-type.h> #include <asm/uaccess.h> #include "internal.h" /* * when plumbing the depths of the key tree, this sets a hard limit set on how * deep we're willing to go */ #define KEYRING_SEARCH_MAX_DEPTH 6 /* * we keep all named keyrings in a hash to speed looking them up */ #define KEYRING_NAME_HASH_SIZE (1 << 5) static struct list_head keyring_name_hash[KEYRING_NAME_HASH_SIZE]; static DEFINE_RWLOCK(keyring_name_lock); static inline unsigned keyring_hash(const char *desc) { unsigned bucket = 0; for (; *desc; desc++) bucket += (unsigned char) *desc; return bucket & (KEYRING_NAME_HASH_SIZE - 1); } /* * the keyring type definition */ static int keyring_instantiate(struct key *keyring, const void *data, size_t datalen); static int keyring_match(const struct key *keyring, const void *criterion); static void keyring_revoke(struct key *keyring); static void keyring_destroy(struct key *keyring); static void keyring_describe(const struct key *keyring, struct seq_file *m); static long keyring_read(const struct key *keyring, char __user *buffer, size_t buflen); struct key_type key_type_keyring = { .name = "keyring", .def_datalen = sizeof(struct keyring_list), .instantiate = keyring_instantiate, .match = keyring_match, .revoke = keyring_revoke, .destroy = keyring_destroy, .describe = keyring_describe, .read = keyring_read, }; EXPORT_SYMBOL(key_type_keyring); /* * semaphore to serialise link/link calls to prevent two link calls in parallel * introducing a cycle */ static DECLARE_RWSEM(keyring_serialise_link_sem); /*****************************************************************************/ /* * publish the name of a keyring so that it can be found by name (if it has * one) */ static void keyring_publish_name(struct key *keyring) { int bucket; if (keyring->description) { bucket = keyring_hash(keyring->description); write_lock(&keyring_name_lock); if (!keyring_name_hash[bucket].next) INIT_LIST_HEAD(&keyring_name_hash[bucket]); list_add_tail(&keyring->type_data.link, &keyring_name_hash[bucket]); write_unlock(&keyring_name_lock); } } /* end keyring_publish_name() */ /*****************************************************************************/ /* * initialise a keyring * - we object if we were given any data */ static int keyring_instantiate(struct key *keyring, const void *data, size_t datalen) { int ret; ret = -EINVAL; if (datalen == 0) { /* make the keyring available by name if it has one */ keyring_publish_name(keyring); ret = 0; } return ret; } /* end keyring_instantiate() */ /*****************************************************************************/ /* * match keyrings on their name */ static int keyring_match(const struct key *keyring, const void *description) { return keyring->description && strcmp(keyring->description, description) == 0; } /* end keyring_match() */ /*****************************************************************************/ /* * dispose of the data dangling from the corpse of a keyring */ static void keyring_destroy(struct key *keyring) { struct keyring_list *klist; int loop; if (keyring->description) { write_lock(&keyring_name_lock); if (keyring->type_data.link.next != NULL && !list_empty(&keyring->type_data.link)) list_del(&keyring->type_data.link); write_unlock(&keyring_name_lock); } klist = rcu_dereference(keyring->payload.subscriptions); if (klist) { for (loop = klist->nkeys - 1; loop >= 0; loop--) key_put(klist->keys[loop]); kfree(klist); } } /* end keyring_destroy() */ /*****************************************************************************/ /* * describe the keyring */ static void keyring_describe(const struct key *keyring, struct seq_file *m) { struct keyring_list *klist; if (keyring->description) { seq_puts(m, keyring->description); } else { seq_puts(m, "[anon]"); } rcu_read_lock(); klist = rcu_dereference(keyring->payload.subscriptions); if (klist) seq_printf(m, ": %u/%u", klist->nkeys, klist->maxkeys); else seq_puts(m, ": empty"); rcu_read_unlock(); } /* end keyring_describe() */ /*****************************************************************************/ /* * read a list of key IDs from the keyring's contents * - the keyring's semaphore is read-locked */ static long keyring_read(const struct key *keyring, char __user *buffer, size_t buflen) { struct keyring_list *klist; struct key *key; size_t qty, tmp; int loop, ret; ret = 0; klist = rcu_dereference(keyring->payload.subscriptions); if (klist) { /* calculate how much data we could return */ qty = klist->nkeys * sizeof(key_serial_t); if (buffer && buflen > 0) { if (buflen > qty) buflen = qty; /* copy the IDs of the subscribed keys into the * buffer */ ret = -EFAULT; for (loop = 0; loop < klist->nkeys; loop++) { key = klist->keys[loop]; tmp = sizeof(key_serial_t); if (tmp > buflen) tmp = buflen; if (copy_to_user(buffer, &key->serial, tmp) != 0) goto error; buflen -= tmp; if (buflen == 0) break; buffer += tmp; } } ret = qty; } error: return ret; } /* end keyring_read() */ /*****************************************************************************/ /* * allocate a keyring and link into the destination keyring */ struct key *keyring_alloc(const char *description, uid_t uid, gid_t gid, const struct cred *cred, unsigned long flags, struct key *dest) { struct key *keyring; int ret; keyring = key_alloc(&key_type_keyring, description, uid, gid, cred, (KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_ALL, flags); if (!IS_ERR(keyring)) { ret = key_instantiate_and_link(keyring, NULL, 0, dest, NULL); if (ret < 0) { key_put(keyring); keyring = ERR_PTR(ret); } } return keyring; } /* end keyring_alloc() */ /*****************************************************************************/ /* * search the supplied keyring tree for a key that matches the criterion * - perform a breadth-then-depth search up to the prescribed limit * - we only find keys on which we have search permission * - we use the supplied match function to see if the description (or other * feature of interest) matches * - we rely on RCU to prevent the keyring lists from disappearing on us * - we return -EAGAIN if we didn't find any matching key * - we return -ENOKEY if we only found negative matching keys * - we propagate the possession attribute from the keyring ref to the key ref */ key_ref_t keyring_search_aux(key_ref_t keyring_ref, const struct cred *cred, struct key_type *type, const void *description, key_match_func_t match) { struct { struct keyring_list *keylist; int kix; } stack[KEYRING_SEARCH_MAX_DEPTH]; struct keyring_list *keylist; struct timespec now; unsigned long possessed, kflags; struct key *keyring, *key; key_ref_t key_ref; long err; int sp, kix; keyring = key_ref_to_ptr(keyring_ref); possessed = is_key_possessed(keyring_ref); key_check(keyring); /* top keyring must have search permission to begin the search */ err = key_task_permission(keyring_ref, cred, KEY_SEARCH); if (err < 0) { key_ref = ERR_PTR(err); goto error; } key_ref = ERR_PTR(-ENOTDIR); if (keyring->type != &key_type_keyring) goto error; rcu_read_lock(); now = current_kernel_time(); err = -EAGAIN; sp = 0; /* firstly we should check to see if this top-level keyring is what we * are looking for */ key_ref = ERR_PTR(-EAGAIN); kflags = keyring->flags; if (keyring->type == type && match(keyring, description)) { key = keyring; /* check it isn't negative and hasn't expired or been * revoked */ if (kflags & (1 << KEY_FLAG_REVOKED)) goto error_2; if (key->expiry && now.tv_sec >= key->expiry) goto error_2; key_ref = ERR_PTR(-ENOKEY); if (kflags & (1 << KEY_FLAG_NEGATIVE)) goto error_2; goto found; } /* otherwise, the top keyring must not be revoked, expired, or * negatively instantiated if we are to search it */ key_ref = ERR_PTR(-EAGAIN); if (kflags & ((1 << KEY_FLAG_REVOKED) | (1 << KEY_FLAG_NEGATIVE)) || (keyring->expiry && now.tv_sec >= keyring->expiry)) goto error_2; /* start processing a new keyring */ descend: if (test_bit(KEY_FLAG_REVOKED, &keyring->flags)) goto not_this_keyring; keylist = rcu_dereference(keyring->payload.subscriptions); if (!keylist) goto not_this_keyring; /* iterate through the keys in this keyring first */ for (kix = 0; kix < keylist->nkeys; kix++) { key = keylist->keys[kix]; kflags = key->flags; /* ignore keys not of this type */ if (key->type != type) continue; /* skip revoked keys and expired keys */ if (kflags & (1 << KEY_FLAG_REVOKED)) continue; if (key->expiry && now.tv_sec >= key->expiry) continue; /* keys that don't match */ if (!match(key, description)) continue; /* key must have search permissions */ if (key_task_permission(make_key_ref(key, possessed), cred, KEY_SEARCH) < 0) continue; /* we set a different error code if we pass a negative key */ if (kflags & (1 << KEY_FLAG_NEGATIVE)) { err = -ENOKEY; continue; } goto found; } /* search through the keyrings nested in this one */ kix = 0; ascend: for (; kix < keylist->nkeys; kix++) { key = keylist->keys[kix]; if (key->type != &key_type_keyring) continue; /* recursively search nested keyrings * - only search keyrings for which we have search permission */ if (sp >= KEYRING_SEARCH_MAX_DEPTH) continue; if (key_task_permission(make_key_ref(key, possessed), cred, KEY_SEARCH) < 0) continue; /* stack the current position */ stack[sp].keylist = keylist; stack[sp].kix = kix; sp++; /* begin again with the new keyring */ keyring = key; goto descend; } /* the keyring we're looking at was disqualified or didn't contain a * matching key */ not_this_keyring: if (sp > 0) { /* resume the processing of a keyring higher up in the tree */ sp--; keylist = stack[sp].keylist; kix = stack[sp].kix + 1; goto ascend; } key_ref = ERR_PTR(err); goto error_2; /* we found a viable match */ found: atomic_inc(&key->usage); key_check(key); key_ref = make_key_ref(key, possessed); error_2: rcu_read_unlock(); error: return key_ref; } /* end keyring_search_aux() */ /*****************************************************************************/ /* * search the supplied keyring tree for a key that matches the criterion * - perform a breadth-then-depth search up to the prescribed limit * - we only find keys on which we have search permission * - we readlock the keyrings as we search down the tree * - we return -EAGAIN if we didn't find any matching key * - we return -ENOKEY if we only found negative matching keys */ key_ref_t keyring_search(key_ref_t keyring, struct key_type *type, const char *description) { if (!type->match) return ERR_PTR(-ENOKEY); return keyring_search_aux(keyring, current->cred, type, description, type->match); } /* end keyring_search() */ EXPORT_SYMBOL(keyring_search); /*****************************************************************************/ /* * search the given keyring only (no recursion) * - keyring must be locked by caller * - caller must guarantee that the keyring is a keyring */ key_ref_t __keyring_search_one(key_ref_t keyring_ref, const struct key_type *ktype, const char *description, key_perm_t perm) { struct keyring_list *klist; unsigned long possessed; struct key *keyring, *key; int loop; keyring = key_ref_to_ptr(keyring_ref); possessed = is_key_possessed(keyring_ref); rcu_read_lock(); klist = rcu_dereference(keyring->payload.subscriptions); if (klist) { for (loop = 0; loop < klist->nkeys; loop++) { key = klist->keys[loop]; if (key->type == ktype && (!key->type->match || key->type->match(key, description)) && key_permission(make_key_ref(key, possessed), perm) == 0 && !test_bit(KEY_FLAG_REVOKED, &key->flags) ) goto found; } } rcu_read_unlock(); return ERR_PTR(-ENOKEY); found: atomic_inc(&key->usage); rcu_read_unlock(); return make_key_ref(key, possessed); } /* end __keyring_search_one() */ /*****************************************************************************/ /* * find a keyring with the specified name * - all named keyrings are searched * - normally only finds keyrings with search permission for the current process */ struct key *find_keyring_by_name(const char *name, bool skip_perm_check) { struct key *keyring; int bucket; keyring = ERR_PTR(-EINVAL); if (!name) goto error; bucket = keyring_hash(name); read_lock(&keyring_name_lock); if (keyring_name_hash[bucket].next) { /* search this hash bucket for a keyring with a matching name * that's readable and that hasn't been revoked */ list_for_each_entry(keyring, &keyring_name_hash[bucket], type_data.link ) { if (keyring->user->user_ns != current_user_ns()) continue; if (test_bit(KEY_FLAG_REVOKED, &keyring->flags)) continue; if (strcmp(keyring->description, name) != 0) continue; if (!skip_perm_check && key_permission(make_key_ref(keyring, 0), KEY_SEARCH) < 0) continue; /* we've got a match */ atomic_inc(&keyring->usage); read_unlock(&keyring_name_lock); goto error; } } read_unlock(&keyring_name_lock); keyring = ERR_PTR(-ENOKEY); error: return keyring; } /* end find_keyring_by_name() */ /*****************************************************************************/ /* * see if a cycle will will be created by inserting acyclic tree B in acyclic * tree A at the topmost level (ie: as a direct child of A) * - since we are adding B to A at the top level, checking for cycles should * just be a matter of seeing if node A is somewhere in tree B */ static int keyring_detect_cycle(struct key *A, struct key *B) { struct { struct keyring_list *keylist; int kix; } stack[KEYRING_SEARCH_MAX_DEPTH]; struct keyring_list *keylist; struct key *subtree, *key; int sp, kix, ret; rcu_read_lock(); ret = -EDEADLK; if (A == B) goto cycle_detected; subtree = B; sp = 0; /* start processing a new keyring */ descend: if (test_bit(KEY_FLAG_REVOKED, &subtree->flags)) goto not_this_keyring; keylist = rcu_dereference(subtree->payload.subscriptions); if (!keylist) goto not_this_keyring; kix = 0; ascend: /* iterate through the remaining keys in this keyring */ for (; kix < keylist->nkeys; kix++) { key = keylist->keys[kix]; if (key == A) goto cycle_detected; /* recursively check nested keyrings */ if (key->type == &key_type_keyring) { if (sp >= KEYRING_SEARCH_MAX_DEPTH) goto too_deep; /* stack the current position */ stack[sp].keylist = keylist; stack[sp].kix = kix; sp++; /* begin again with the new keyring */ subtree = key; goto descend; } } /* the keyring we're looking at was disqualified or didn't contain a * matching key */ not_this_keyring: if (sp > 0) { /* resume the checking of a keyring higher up in the tree */ sp--; keylist = stack[sp].keylist; kix = stack[sp].kix + 1; goto ascend; } ret = 0; /* no cycles detected */ error: rcu_read_unlock(); return ret; too_deep: ret = -ELOOP; goto error; cycle_detected: ret = -EDEADLK; goto error; } /* end keyring_detect_cycle() */ /*****************************************************************************/ /* * dispose of a keyring list after the RCU grace period */ static void keyring_link_rcu_disposal(struct rcu_head *rcu) { struct keyring_list *klist = container_of(rcu, struct keyring_list, rcu); kfree(klist); } /* end keyring_link_rcu_disposal() */ /*****************************************************************************/ /* * dispose of a keyring list after the RCU grace period, freeing the unlinked * key */ static void keyring_unlink_rcu_disposal(struct rcu_head *rcu) { struct keyring_list *klist = container_of(rcu, struct keyring_list, rcu); key_put(klist->keys[klist->delkey]); kfree(klist); } /* end keyring_unlink_rcu_disposal() */ /*****************************************************************************/ /* * link a key into to a keyring * - must be called with the keyring's semaphore write-locked * - discard already extant link to matching key if there is one */ int __key_link(struct key *keyring, struct key *key) { struct keyring_list *klist, *nklist; unsigned max; size_t size; int loop, ret; ret = -EKEYREVOKED; if (test_bit(KEY_FLAG_REVOKED, &keyring->flags)) goto error; ret = -ENOTDIR; if (keyring->type != &key_type_keyring) goto error; /* serialise link/link calls to prevent parallel calls causing a * cycle when applied to two keyring in opposite orders */ down_write(&keyring_serialise_link_sem); /* check that we aren't going to create a cycle adding one keyring to * another */ if (key->type == &key_type_keyring) { ret = keyring_detect_cycle(keyring, key); if (ret < 0) goto error2; } /* see if there's a matching key we can displace */ klist = keyring->payload.subscriptions; if (klist && klist->nkeys > 0) { struct key_type *type = key->type; for (loop = klist->nkeys - 1; loop >= 0; loop--) { if (klist->keys[loop]->type == type && strcmp(klist->keys[loop]->description, key->description) == 0 ) { /* found a match - replace with new key */ size = sizeof(struct key *) * klist->maxkeys; size += sizeof(*klist); BUG_ON(size > PAGE_SIZE); ret = -ENOMEM; nklist = kmemdup(klist, size, GFP_KERNEL); if (!nklist) goto error2; /* replace matched key */ atomic_inc(&key->usage); nklist->keys[loop] = key; rcu_assign_pointer( keyring->payload.subscriptions, nklist); /* dispose of the old keyring list and the * displaced key */ klist->delkey = loop; call_rcu(&klist->rcu, keyring_unlink_rcu_disposal); goto done; } } } /* check that we aren't going to overrun the user's quota */ ret = key_payload_reserve(keyring, keyring->datalen + KEYQUOTA_LINK_BYTES); if (ret < 0) goto error2; klist = keyring->payload.subscriptions; if (klist && klist->nkeys < klist->maxkeys) { /* there's sufficient slack space to add directly */ atomic_inc(&key->usage); klist->keys[klist->nkeys] = key; smp_wmb(); klist->nkeys++; smp_wmb(); } else { /* grow the key list */ max = 4; if (klist) max += klist->maxkeys; ret = -ENFILE; if (max > 65535) goto error3; size = sizeof(*klist) + sizeof(struct key *) * max; if (size > PAGE_SIZE) goto error3; ret = -ENOMEM; nklist = kmalloc(size, GFP_KERNEL); if (!nklist) goto error3; nklist->maxkeys = max; nklist->nkeys = 0; if (klist) { nklist->nkeys = klist->nkeys; memcpy(nklist->keys, klist->keys, sizeof(struct key *) * klist->nkeys); } /* add the key into the new space */ atomic_inc(&key->usage); nklist->keys[nklist->nkeys++] = key; rcu_assign_pointer(keyring->payload.subscriptions, nklist); /* dispose of the old keyring list */ if (klist) call_rcu(&klist->rcu, keyring_link_rcu_disposal); } done: ret = 0; error2: up_write(&keyring_serialise_link_sem); error: return ret; error3: /* undo the quota changes */ key_payload_reserve(keyring, keyring->datalen - KEYQUOTA_LINK_BYTES); goto error2; } /* end __key_link() */ /*****************************************************************************/ /* * link a key to a keyring */ int key_link(struct key *keyring, struct key *key) { int ret; key_check(keyring); key_check(key); down_write(&keyring->sem); ret = __key_link(keyring, key); up_write(&keyring->sem); return ret; } /* end key_link() */ EXPORT_SYMBOL(key_link); /*****************************************************************************/ /* * unlink the first link to a key from a keyring */ int key_unlink(struct key *keyring, struct key *key) { struct keyring_list *klist, *nklist; int loop, ret; key_check(keyring); key_check(key); ret = -ENOTDIR; if (keyring->type != &key_type_keyring) goto error; down_write(&keyring->sem); klist = keyring->payload.subscriptions; if (klist) { /* search the keyring for the key */ for (loop = 0; loop < klist->nkeys; loop++) if (klist->keys[loop] == key) goto key_is_present; } up_write(&keyring->sem); ret = -ENOENT; goto error; key_is_present: /* we need to copy the key list for RCU purposes */ nklist = kmalloc(sizeof(*klist) + sizeof(struct key *) * klist->maxkeys, GFP_KERNEL); if (!nklist) goto nomem; nklist->maxkeys = klist->maxkeys; nklist->nkeys = klist->nkeys - 1; if (loop > 0) memcpy(&nklist->keys[0], &klist->keys[0], loop * sizeof(struct key *)); if (loop < nklist->nkeys) memcpy(&nklist->keys[loop], &klist->keys[loop + 1], (nklist->nkeys - loop) * sizeof(struct key *)); /* adjust the user's quota */ key_payload_reserve(keyring, keyring->datalen - KEYQUOTA_LINK_BYTES); rcu_assign_pointer(keyring->payload.subscriptions, nklist); up_write(&keyring->sem); /* schedule for later cleanup */ klist->delkey = loop; call_rcu(&klist->rcu, keyring_unlink_rcu_disposal); ret = 0; error: return ret; nomem: ret = -ENOMEM; up_write(&keyring->sem); goto error; } /* end key_unlink() */ EXPORT_SYMBOL(key_unlink); /*****************************************************************************/ /* * dispose of a keyring list after the RCU grace period, releasing the keys it * links to */ static void keyring_clear_rcu_disposal(struct rcu_head *rcu) { struct keyring_list *klist; int loop; klist = container_of(rcu, struct keyring_list, rcu); for (loop = klist->nkeys - 1; loop >= 0; loop--) key_put(klist->keys[loop]); kfree(klist); } /* end keyring_clear_rcu_disposal() */ /*****************************************************************************/ /* * clear the specified process keyring * - implements keyctl(KEYCTL_CLEAR) */ int keyring_clear(struct key *keyring) { struct keyring_list *klist; int ret; ret = -ENOTDIR; if (keyring->type == &key_type_keyring) { /* detach the pointer block with the locks held */ down_write(&keyring->sem); klist = keyring->payload.subscriptions; if (klist) { /* adjust the quota */ key_payload_reserve(keyring, sizeof(struct keyring_list)); rcu_assign_pointer(keyring->payload.subscriptions, NULL); } up_write(&keyring->sem); /* free the keys after the locks have been dropped */ if (klist) call_rcu(&klist->rcu, keyring_clear_rcu_disposal); ret = 0; } return ret; } /* end keyring_clear() */ EXPORT_SYMBOL(keyring_clear); /*****************************************************************************/ /* * dispose of the links from a revoked keyring * - called with the key sem write-locked */ static void keyring_revoke(struct key *keyring) { struct keyring_list *klist = keyring->payload.subscriptions; /* adjust the quota */ key_payload_reserve(keyring, 0); if (klist) { rcu_assign_pointer(keyring->payload.subscriptions, NULL); call_rcu(&klist->rcu, keyring_clear_rcu_disposal); } } /* end keyring_revoke() */ /* * Determine whether a key is dead */ static bool key_is_dead(struct key *key, time_t limit) { return test_bit(KEY_FLAG_DEAD, &key->flags) || (key->expiry > 0 && key->expiry <= limit); } /* * Collect garbage from the contents of a keyring */ void keyring_gc(struct key *keyring, time_t limit) { struct keyring_list *klist, *new; struct key *key; int loop, keep, max; kenter("{%x,%s}", key_serial(keyring), keyring->description); down_write(&keyring->sem); klist = keyring->payload.subscriptions; if (!klist) goto no_klist; /* work out how many subscriptions we're keeping */ keep = 0; for (loop = klist->nkeys - 1; loop >= 0; loop--) if (!key_is_dead(klist->keys[loop], limit)) keep++; if (keep == klist->nkeys) goto just_return; /* allocate a new keyring payload */ max = roundup(keep, 4); new = kmalloc(sizeof(struct keyring_list) + max * sizeof(struct key *), GFP_KERNEL); if (!new) goto nomem; new->maxkeys = max; new->nkeys = 0; new->delkey = 0; /* install the live keys * - must take care as expired keys may be updated back to life */ keep = 0; for (loop = klist->nkeys - 1; loop >= 0; loop--) { key = klist->keys[loop]; if (!key_is_dead(key, limit)) { if (keep >= max) goto discard_new; new->keys[keep++] = key_get(key); } } new->nkeys = keep; /* adjust the quota */ key_payload_reserve(keyring, sizeof(struct keyring_list) + KEYQUOTA_LINK_BYTES * keep); if (keep == 0) { rcu_assign_pointer(keyring->payload.subscriptions, NULL); kfree(new); } else { rcu_assign_pointer(keyring->payload.subscriptions, new); } up_write(&keyring->sem); call_rcu(&klist->rcu, keyring_clear_rcu_disposal); kleave(" [yes]"); return; discard_new: new->nkeys = keep; keyring_clear_rcu_disposal(&new->rcu); up_write(&keyring->sem); kleave(" [discard]"); return; just_return: up_write(&keyring->sem); kleave(" [no dead]"); return; no_klist: up_write(&keyring->sem); kleave(" [no_klist]"); return; nomem: up_write(&keyring->sem); kleave(" [oom]"); }