Refactor kallsyms_show_value() users for correct cred
Several users of kallsyms_show_value() were performing checks not during "open". Refactor everything needed to gain proper checks against file->f_cred for modules, kprobes, and bpf. -----BEGIN PGP SIGNATURE----- iQJKBAABCgA0FiEEpcP2jyKd1g9yPm4TiXL039xtwCYFAl8GUbMWHGtlZXNjb29r QGNocm9taXVtLm9yZwAKCRCJcvTf3G3AJohnD/9VsPsAMV+8lhsPvkcuW/DkTcAY qUEzsXU3v06gJ0Z/1lBKtisJ6XmD93wWcZCTFvJ0S8vR3yLZvOVfToVjCMO32Trc 4ZkWTPwpvfeLug6T6CcI2ukQdZ/opI1cSabqGl79arSBgE/tsghwrHuJ8Exkz4uq 0b7i8nZa+RiTezwx4EVeGcg6Dv1tG5UTG2VQvD/+QGGKneBlrlaKlI885N/6jsHa KxvB7+8ES1pnfGYZenx+RxMdljNrtyptbQEU8gyvoV5YR7635gjZsVsPwWANJo+4 EGcFFpwWOAcVQaC3dareLTM8nVngU6Wl3Rd7JjZtjvtZba8DdCn669R34zDGXbiP +1n1dYYMSMBeqVUbAQfQyLD0pqMIHdwQj2TN8thSGccr2o3gNk6AXgYq0aYm8IBf xDCvAansJw9WqmxErIIsD4BFkMqF7MjH3eYZxwCPWSrKGDvKxQSPV5FarnpDC9U7 dYCWVxNPmtn+unC/53yXjEcBepKaYgNR7j5G7uOfkHvU43Bd5demzLiVJ10D8abJ ezyErxxEqX2Gr7JR2fWv7iBbULJViqcAnYjdl0y0NgK/hftt98iuge6cZmt1z6ai 24vI3X4VhvvVN5/f64cFDAdYtMRUtOo2dmxdXMid1NI07Mj2qFU1MUwb8RHHlxbK 8UegV2zcrBghnVuMkw== =ib5Q -----END PGP SIGNATURE----- Merge tag 'kallsyms_show_value-v5.8-rc5' of git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux Pull kallsyms fix from Kees Cook: "Refactor kallsyms_show_value() users for correct cred. I'm not delighted by the timing of getting these changes to you, but it does fix a handful of kernel address exposures, and no one has screamed yet at the patches. Several users of kallsyms_show_value() were performing checks not during "open". Refactor everything needed to gain proper checks against file->f_cred for modules, kprobes, and bpf" * tag 'kallsyms_show_value-v5.8-rc5' of git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux: selftests: kmod: Add module address visibility test bpf: Check correct cred for CAP_SYSLOG in bpf_dump_raw_ok() kprobes: Do not expose probe addresses to non-CAP_SYSLOG module: Do not expose section addresses to non-CAP_SYSLOG module: Refactor section attr into bin attribute kallsyms: Refactor kallsyms_show_value() to take cred
This commit is contained in:
commit
ce69fb3b39
|
@ -884,12 +884,12 @@ void bpf_jit_compile(struct bpf_prog *prog);
|
|||
bool bpf_jit_needs_zext(void);
|
||||
bool bpf_helper_changes_pkt_data(void *func);
|
||||
|
||||
static inline bool bpf_dump_raw_ok(void)
|
||||
static inline bool bpf_dump_raw_ok(const struct cred *cred)
|
||||
{
|
||||
/* Reconstruction of call-sites is dependent on kallsyms,
|
||||
* thus make dump the same restriction.
|
||||
*/
|
||||
return kallsyms_show_value() == 1;
|
||||
return kallsyms_show_value(cred);
|
||||
}
|
||||
|
||||
struct bpf_prog *bpf_patch_insn_single(struct bpf_prog *prog, u32 off,
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#define KSYM_SYMBOL_LEN (sizeof("%s+%#lx/%#lx [%s]") + (KSYM_NAME_LEN - 1) + \
|
||||
2*(BITS_PER_LONG*3/10) + (MODULE_NAME_LEN - 1) + 1)
|
||||
|
||||
struct cred;
|
||||
struct module;
|
||||
|
||||
static inline int is_kernel_inittext(unsigned long addr)
|
||||
|
@ -98,7 +99,7 @@ int lookup_symbol_name(unsigned long addr, char *symname);
|
|||
int lookup_symbol_attrs(unsigned long addr, unsigned long *size, unsigned long *offset, char *modname, char *name);
|
||||
|
||||
/* How and when do we show kallsyms values? */
|
||||
extern int kallsyms_show_value(void);
|
||||
extern bool kallsyms_show_value(const struct cred *cred);
|
||||
|
||||
#else /* !CONFIG_KALLSYMS */
|
||||
|
||||
|
@ -158,7 +159,7 @@ static inline int lookup_symbol_attrs(unsigned long addr, unsigned long *size, u
|
|||
return -ERANGE;
|
||||
}
|
||||
|
||||
static inline int kallsyms_show_value(void)
|
||||
static inline bool kallsyms_show_value(const struct cred *cred)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -3139,7 +3139,8 @@ static const struct bpf_map *bpf_map_from_imm(const struct bpf_prog *prog,
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static struct bpf_insn *bpf_insn_prepare_dump(const struct bpf_prog *prog)
|
||||
static struct bpf_insn *bpf_insn_prepare_dump(const struct bpf_prog *prog,
|
||||
const struct cred *f_cred)
|
||||
{
|
||||
const struct bpf_map *map;
|
||||
struct bpf_insn *insns;
|
||||
|
@ -3165,7 +3166,7 @@ static struct bpf_insn *bpf_insn_prepare_dump(const struct bpf_prog *prog)
|
|||
code == (BPF_JMP | BPF_CALL_ARGS)) {
|
||||
if (code == (BPF_JMP | BPF_CALL_ARGS))
|
||||
insns[i].code = BPF_JMP | BPF_CALL;
|
||||
if (!bpf_dump_raw_ok())
|
||||
if (!bpf_dump_raw_ok(f_cred))
|
||||
insns[i].imm = 0;
|
||||
continue;
|
||||
}
|
||||
|
@ -3221,7 +3222,8 @@ static int set_info_rec_size(struct bpf_prog_info *info)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int bpf_prog_get_info_by_fd(struct bpf_prog *prog,
|
||||
static int bpf_prog_get_info_by_fd(struct file *file,
|
||||
struct bpf_prog *prog,
|
||||
const union bpf_attr *attr,
|
||||
union bpf_attr __user *uattr)
|
||||
{
|
||||
|
@ -3290,11 +3292,11 @@ static int bpf_prog_get_info_by_fd(struct bpf_prog *prog,
|
|||
struct bpf_insn *insns_sanitized;
|
||||
bool fault;
|
||||
|
||||
if (prog->blinded && !bpf_dump_raw_ok()) {
|
||||
if (prog->blinded && !bpf_dump_raw_ok(file->f_cred)) {
|
||||
info.xlated_prog_insns = 0;
|
||||
goto done;
|
||||
}
|
||||
insns_sanitized = bpf_insn_prepare_dump(prog);
|
||||
insns_sanitized = bpf_insn_prepare_dump(prog, file->f_cred);
|
||||
if (!insns_sanitized)
|
||||
return -ENOMEM;
|
||||
uinsns = u64_to_user_ptr(info.xlated_prog_insns);
|
||||
|
@ -3328,7 +3330,7 @@ static int bpf_prog_get_info_by_fd(struct bpf_prog *prog,
|
|||
}
|
||||
|
||||
if (info.jited_prog_len && ulen) {
|
||||
if (bpf_dump_raw_ok()) {
|
||||
if (bpf_dump_raw_ok(file->f_cred)) {
|
||||
uinsns = u64_to_user_ptr(info.jited_prog_insns);
|
||||
ulen = min_t(u32, info.jited_prog_len, ulen);
|
||||
|
||||
|
@ -3363,7 +3365,7 @@ static int bpf_prog_get_info_by_fd(struct bpf_prog *prog,
|
|||
ulen = info.nr_jited_ksyms;
|
||||
info.nr_jited_ksyms = prog->aux->func_cnt ? : 1;
|
||||
if (ulen) {
|
||||
if (bpf_dump_raw_ok()) {
|
||||
if (bpf_dump_raw_ok(file->f_cred)) {
|
||||
unsigned long ksym_addr;
|
||||
u64 __user *user_ksyms;
|
||||
u32 i;
|
||||
|
@ -3394,7 +3396,7 @@ static int bpf_prog_get_info_by_fd(struct bpf_prog *prog,
|
|||
ulen = info.nr_jited_func_lens;
|
||||
info.nr_jited_func_lens = prog->aux->func_cnt ? : 1;
|
||||
if (ulen) {
|
||||
if (bpf_dump_raw_ok()) {
|
||||
if (bpf_dump_raw_ok(file->f_cred)) {
|
||||
u32 __user *user_lens;
|
||||
u32 func_len, i;
|
||||
|
||||
|
@ -3451,7 +3453,7 @@ static int bpf_prog_get_info_by_fd(struct bpf_prog *prog,
|
|||
else
|
||||
info.nr_jited_line_info = 0;
|
||||
if (info.nr_jited_line_info && ulen) {
|
||||
if (bpf_dump_raw_ok()) {
|
||||
if (bpf_dump_raw_ok(file->f_cred)) {
|
||||
__u64 __user *user_linfo;
|
||||
u32 i;
|
||||
|
||||
|
@ -3497,7 +3499,8 @@ done:
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int bpf_map_get_info_by_fd(struct bpf_map *map,
|
||||
static int bpf_map_get_info_by_fd(struct file *file,
|
||||
struct bpf_map *map,
|
||||
const union bpf_attr *attr,
|
||||
union bpf_attr __user *uattr)
|
||||
{
|
||||
|
@ -3540,7 +3543,8 @@ static int bpf_map_get_info_by_fd(struct bpf_map *map,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int bpf_btf_get_info_by_fd(struct btf *btf,
|
||||
static int bpf_btf_get_info_by_fd(struct file *file,
|
||||
struct btf *btf,
|
||||
const union bpf_attr *attr,
|
||||
union bpf_attr __user *uattr)
|
||||
{
|
||||
|
@ -3555,7 +3559,8 @@ static int bpf_btf_get_info_by_fd(struct btf *btf,
|
|||
return btf_get_info_by_fd(btf, attr, uattr);
|
||||
}
|
||||
|
||||
static int bpf_link_get_info_by_fd(struct bpf_link *link,
|
||||
static int bpf_link_get_info_by_fd(struct file *file,
|
||||
struct bpf_link *link,
|
||||
const union bpf_attr *attr,
|
||||
union bpf_attr __user *uattr)
|
||||
{
|
||||
|
@ -3608,15 +3613,15 @@ static int bpf_obj_get_info_by_fd(const union bpf_attr *attr,
|
|||
return -EBADFD;
|
||||
|
||||
if (f.file->f_op == &bpf_prog_fops)
|
||||
err = bpf_prog_get_info_by_fd(f.file->private_data, attr,
|
||||
err = bpf_prog_get_info_by_fd(f.file, f.file->private_data, attr,
|
||||
uattr);
|
||||
else if (f.file->f_op == &bpf_map_fops)
|
||||
err = bpf_map_get_info_by_fd(f.file->private_data, attr,
|
||||
err = bpf_map_get_info_by_fd(f.file, f.file->private_data, attr,
|
||||
uattr);
|
||||
else if (f.file->f_op == &btf_fops)
|
||||
err = bpf_btf_get_info_by_fd(f.file->private_data, attr, uattr);
|
||||
err = bpf_btf_get_info_by_fd(f.file, f.file->private_data, attr, uattr);
|
||||
else if (f.file->f_op == &bpf_link_fops)
|
||||
err = bpf_link_get_info_by_fd(f.file->private_data,
|
||||
err = bpf_link_get_info_by_fd(f.file, f.file->private_data,
|
||||
attr, uattr);
|
||||
else
|
||||
err = -EINVAL;
|
||||
|
|
|
@ -644,19 +644,20 @@ static inline int kallsyms_for_perf(void)
|
|||
* Otherwise, require CAP_SYSLOG (assuming kptr_restrict isn't set to
|
||||
* block even that).
|
||||
*/
|
||||
int kallsyms_show_value(void)
|
||||
bool kallsyms_show_value(const struct cred *cred)
|
||||
{
|
||||
switch (kptr_restrict) {
|
||||
case 0:
|
||||
if (kallsyms_for_perf())
|
||||
return 1;
|
||||
return true;
|
||||
/* fallthrough */
|
||||
case 1:
|
||||
if (has_capability_noaudit(current, CAP_SYSLOG))
|
||||
return 1;
|
||||
if (security_capable(cred, &init_user_ns, CAP_SYSLOG,
|
||||
CAP_OPT_NOAUDIT) == 0)
|
||||
return true;
|
||||
/* fallthrough */
|
||||
default:
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -673,7 +674,11 @@ static int kallsyms_open(struct inode *inode, struct file *file)
|
|||
return -ENOMEM;
|
||||
reset_iter(iter, 0);
|
||||
|
||||
iter->show_value = kallsyms_show_value();
|
||||
/*
|
||||
* Instead of checking this on every s_show() call, cache
|
||||
* the result here at open time.
|
||||
*/
|
||||
iter->show_value = kallsyms_show_value(file->f_cred);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -2448,7 +2448,7 @@ static void report_probe(struct seq_file *pi, struct kprobe *p,
|
|||
else
|
||||
kprobe_type = "k";
|
||||
|
||||
if (!kallsyms_show_value())
|
||||
if (!kallsyms_show_value(pi->file->f_cred))
|
||||
addr = NULL;
|
||||
|
||||
if (sym)
|
||||
|
@ -2540,7 +2540,7 @@ static int kprobe_blacklist_seq_show(struct seq_file *m, void *v)
|
|||
* If /proc/kallsyms is not showing kernel address, we won't
|
||||
* show them here either.
|
||||
*/
|
||||
if (!kallsyms_show_value())
|
||||
if (!kallsyms_show_value(m->file->f_cred))
|
||||
seq_printf(m, "0x%px-0x%px\t%ps\n", NULL, NULL,
|
||||
(void *)ent->start_addr);
|
||||
else
|
||||
|
|
|
@ -1510,8 +1510,7 @@ static inline bool sect_empty(const Elf_Shdr *sect)
|
|||
}
|
||||
|
||||
struct module_sect_attr {
|
||||
struct module_attribute mattr;
|
||||
char *name;
|
||||
struct bin_attribute battr;
|
||||
unsigned long address;
|
||||
};
|
||||
|
||||
|
@ -1521,13 +1520,18 @@ struct module_sect_attrs {
|
|||
struct module_sect_attr attrs[];
|
||||
};
|
||||
|
||||
static ssize_t module_sect_show(struct module_attribute *mattr,
|
||||
struct module_kobject *mk, char *buf)
|
||||
static ssize_t module_sect_read(struct file *file, struct kobject *kobj,
|
||||
struct bin_attribute *battr,
|
||||
char *buf, loff_t pos, size_t count)
|
||||
{
|
||||
struct module_sect_attr *sattr =
|
||||
container_of(mattr, struct module_sect_attr, mattr);
|
||||
return sprintf(buf, "0x%px\n", kptr_restrict < 2 ?
|
||||
(void *)sattr->address : NULL);
|
||||
container_of(battr, struct module_sect_attr, battr);
|
||||
|
||||
if (pos != 0)
|
||||
return -EINVAL;
|
||||
|
||||
return sprintf(buf, "0x%px\n",
|
||||
kallsyms_show_value(file->f_cred) ? (void *)sattr->address : NULL);
|
||||
}
|
||||
|
||||
static void free_sect_attrs(struct module_sect_attrs *sect_attrs)
|
||||
|
@ -1535,7 +1539,7 @@ static void free_sect_attrs(struct module_sect_attrs *sect_attrs)
|
|||
unsigned int section;
|
||||
|
||||
for (section = 0; section < sect_attrs->nsections; section++)
|
||||
kfree(sect_attrs->attrs[section].name);
|
||||
kfree(sect_attrs->attrs[section].battr.attr.name);
|
||||
kfree(sect_attrs);
|
||||
}
|
||||
|
||||
|
@ -1544,42 +1548,41 @@ static void add_sect_attrs(struct module *mod, const struct load_info *info)
|
|||
unsigned int nloaded = 0, i, size[2];
|
||||
struct module_sect_attrs *sect_attrs;
|
||||
struct module_sect_attr *sattr;
|
||||
struct attribute **gattr;
|
||||
struct bin_attribute **gattr;
|
||||
|
||||
/* Count loaded sections and allocate structures */
|
||||
for (i = 0; i < info->hdr->e_shnum; i++)
|
||||
if (!sect_empty(&info->sechdrs[i]))
|
||||
nloaded++;
|
||||
size[0] = ALIGN(struct_size(sect_attrs, attrs, nloaded),
|
||||
sizeof(sect_attrs->grp.attrs[0]));
|
||||
size[1] = (nloaded + 1) * sizeof(sect_attrs->grp.attrs[0]);
|
||||
sizeof(sect_attrs->grp.bin_attrs[0]));
|
||||
size[1] = (nloaded + 1) * sizeof(sect_attrs->grp.bin_attrs[0]);
|
||||
sect_attrs = kzalloc(size[0] + size[1], GFP_KERNEL);
|
||||
if (sect_attrs == NULL)
|
||||
return;
|
||||
|
||||
/* Setup section attributes. */
|
||||
sect_attrs->grp.name = "sections";
|
||||
sect_attrs->grp.attrs = (void *)sect_attrs + size[0];
|
||||
sect_attrs->grp.bin_attrs = (void *)sect_attrs + size[0];
|
||||
|
||||
sect_attrs->nsections = 0;
|
||||
sattr = §_attrs->attrs[0];
|
||||
gattr = §_attrs->grp.attrs[0];
|
||||
gattr = §_attrs->grp.bin_attrs[0];
|
||||
for (i = 0; i < info->hdr->e_shnum; i++) {
|
||||
Elf_Shdr *sec = &info->sechdrs[i];
|
||||
if (sect_empty(sec))
|
||||
continue;
|
||||
sysfs_bin_attr_init(&sattr->battr);
|
||||
sattr->address = sec->sh_addr;
|
||||
sattr->name = kstrdup(info->secstrings + sec->sh_name,
|
||||
GFP_KERNEL);
|
||||
if (sattr->name == NULL)
|
||||
sattr->battr.attr.name =
|
||||
kstrdup(info->secstrings + sec->sh_name, GFP_KERNEL);
|
||||
if (sattr->battr.attr.name == NULL)
|
||||
goto out;
|
||||
sect_attrs->nsections++;
|
||||
sysfs_attr_init(&sattr->mattr.attr);
|
||||
sattr->mattr.show = module_sect_show;
|
||||
sattr->mattr.store = NULL;
|
||||
sattr->mattr.attr.name = sattr->name;
|
||||
sattr->mattr.attr.mode = S_IRUSR;
|
||||
*(gattr++) = &(sattr++)->mattr.attr;
|
||||
sattr->battr.read = module_sect_read;
|
||||
sattr->battr.size = 3 /* "0x", "\n" */ + (BITS_PER_LONG / 4);
|
||||
sattr->battr.attr.mode = 0400;
|
||||
*(gattr++) = &(sattr++)->battr;
|
||||
}
|
||||
*gattr = NULL;
|
||||
|
||||
|
@ -1669,7 +1672,7 @@ static void add_notes_attrs(struct module *mod, const struct load_info *info)
|
|||
continue;
|
||||
if (info->sechdrs[i].sh_type == SHT_NOTE) {
|
||||
sysfs_bin_attr_init(nattr);
|
||||
nattr->attr.name = mod->sect_attrs->attrs[loaded].name;
|
||||
nattr->attr.name = mod->sect_attrs->attrs[loaded].battr.attr.name;
|
||||
nattr->attr.mode = S_IRUGO;
|
||||
nattr->size = info->sechdrs[i].sh_size;
|
||||
nattr->private = (void *) info->sechdrs[i].sh_addr;
|
||||
|
@ -4379,7 +4382,7 @@ static int modules_open(struct inode *inode, struct file *file)
|
|||
|
||||
if (!err) {
|
||||
struct seq_file *m = file->private_data;
|
||||
m->private = kallsyms_show_value() ? NULL : (void *)8ul;
|
||||
m->private = kallsyms_show_value(file->f_cred) ? NULL : (void *)8ul;
|
||||
}
|
||||
|
||||
return err;
|
||||
|
|
|
@ -274,7 +274,7 @@ static int proc_dointvec_minmax_bpf_enable(struct ctl_table *table, int write,
|
|||
ret = proc_dointvec_minmax(&tmp, write, buffer, lenp, ppos);
|
||||
if (write && !ret) {
|
||||
if (jit_enable < 2 ||
|
||||
(jit_enable == 2 && bpf_dump_raw_ok())) {
|
||||
(jit_enable == 2 && bpf_dump_raw_ok(current_cred()))) {
|
||||
*(int *)table->data = jit_enable;
|
||||
if (jit_enable == 2)
|
||||
pr_warn("bpf_jit_enable = 2 was set! NEVER use this in production, only for JIT debugging!\n");
|
||||
|
|
|
@ -63,6 +63,8 @@ ALL_TESTS="$ALL_TESTS 0008:150:1"
|
|||
ALL_TESTS="$ALL_TESTS 0009:150:1"
|
||||
ALL_TESTS="$ALL_TESTS 0010:1:1"
|
||||
ALL_TESTS="$ALL_TESTS 0011:1:1"
|
||||
ALL_TESTS="$ALL_TESTS 0012:1:1"
|
||||
ALL_TESTS="$ALL_TESTS 0013:1:1"
|
||||
|
||||
# Kselftest framework requirement - SKIP code is 4.
|
||||
ksft_skip=4
|
||||
|
@ -470,6 +472,38 @@ kmod_test_0011()
|
|||
echo "$MODPROBE" > /proc/sys/kernel/modprobe
|
||||
}
|
||||
|
||||
kmod_check_visibility()
|
||||
{
|
||||
local name="$1"
|
||||
local cmd="$2"
|
||||
|
||||
modprobe $DEFAULT_KMOD_DRIVER
|
||||
|
||||
local priv=$(eval $cmd)
|
||||
local unpriv=$(capsh --drop=CAP_SYSLOG -- -c "$cmd")
|
||||
|
||||
if [ "$priv" = "$unpriv" ] || \
|
||||
[ "${priv:0:3}" = "0x0" ] || \
|
||||
[ "${unpriv:0:3}" != "0x0" ] ; then
|
||||
echo "${FUNCNAME[0]}: FAIL, $name visible to unpriv: '$priv' vs '$unpriv'" >&2
|
||||
exit 1
|
||||
else
|
||||
echo "${FUNCNAME[0]}: OK!"
|
||||
fi
|
||||
}
|
||||
|
||||
kmod_test_0012()
|
||||
{
|
||||
kmod_check_visibility /proc/modules \
|
||||
"grep '^${DEFAULT_KMOD_DRIVER}\b' /proc/modules | awk '{print \$NF}'"
|
||||
}
|
||||
|
||||
kmod_test_0013()
|
||||
{
|
||||
kmod_check_visibility '/sys/module/*/sections/*' \
|
||||
"cat /sys/module/${DEFAULT_KMOD_DRIVER}/sections/.*text | head -n1"
|
||||
}
|
||||
|
||||
list_tests()
|
||||
{
|
||||
echo "Test ID list:"
|
||||
|
@ -489,6 +523,8 @@ list_tests()
|
|||
echo "0009 x $(get_test_count 0009) - multithreaded - push kmod_concurrent over max_modprobes for get_fs_type()"
|
||||
echo "0010 x $(get_test_count 0010) - test nonexistent modprobe path"
|
||||
echo "0011 x $(get_test_count 0011) - test completely disabling module autoloading"
|
||||
echo "0012 x $(get_test_count 0012) - test /proc/modules address visibility under CAP_SYSLOG"
|
||||
echo "0013 x $(get_test_count 0013) - test /sys/module/*/sections/* visibility under CAP_SYSLOG"
|
||||
}
|
||||
|
||||
usage()
|
||||
|
|
Loading…
Reference in New Issue