Viewing: lproc_gss.c
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
* Use is subject to license terms.
*
* Copyright (c) 2012, 2016, Intel Corporation.
*/
/*
* This file is part of Lustre, http://www.lustre.org/
*/
#define DEBUG_SUBSYSTEM S_SEC
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/dcache.h>
#include <linux/fs.h>
#include <linux/mutex.h>
#include <obd.h>
#include <obd_class.h>
#include <obd_support.h>
#include <lustre_net.h>
#include <lustre_import.h>
#include <lprocfs_status.h>
#include <lustre_sec.h>
#include "gss_err.h"
#include "gss_internal.h"
#include "gss_api.h"
static struct dentry *gss_debugfs_dir;
static struct kobject *gss_kobj;
static struct kobject *gss_kobj_lk;
/*
* statistic of "out-of-sequence-window"
*/
static struct {
spinlock_t oos_lock;
atomic_t oos_cli_count; /* client occurrence */
int oos_cli_behind; /* client max seqs behind */
atomic_t oos_svc_replay[3]; /* server replay detected */
atomic_t oos_svc_pass[3]; /* server verified ok */
} gss_stat_oos = {
.oos_cli_count = ATOMIC_INIT(0),
.oos_cli_behind = 0,
.oos_svc_replay = { ATOMIC_INIT(0), },
.oos_svc_pass = { ATOMIC_INIT(0), },
};
void gss_stat_oos_record_cli(int behind)
{
atomic_inc(&gss_stat_oos.oos_cli_count);
spin_lock(&gss_stat_oos.oos_lock);
if (behind > gss_stat_oos.oos_cli_behind)
gss_stat_oos.oos_cli_behind = behind;
spin_unlock(&gss_stat_oos.oos_lock);
}
void gss_stat_oos_record_svc(int phase, int replay)
{
LASSERT(phase >= 0 && phase <= 2);
if (replay)
atomic_inc(&gss_stat_oos.oos_svc_replay[phase]);
else
atomic_inc(&gss_stat_oos.oos_svc_pass[phase]);
}
static int gss_proc_oos_seq_show(struct seq_file *m, void *v)
{
seq_printf(m, "seqwin: %u\n"
"backwin: %u\n"
"client fall behind seqwin\n"
" occurrence: %d\n"
" max seq behind: %d\n"
"server replay detected:\n"
" phase 0: %d\n"
" phase 1: %d\n"
" phase 2: %d\n"
"server verify ok:\n"
" phase 2: %d\n",
GSS_SEQ_WIN_MAIN,
GSS_SEQ_WIN_BACK,
atomic_read(&gss_stat_oos.oos_cli_count),
gss_stat_oos.oos_cli_behind,
atomic_read(&gss_stat_oos.oos_svc_replay[0]),
atomic_read(&gss_stat_oos.oos_svc_replay[1]),
atomic_read(&gss_stat_oos.oos_svc_replay[2]),
atomic_read(&gss_stat_oos.oos_svc_pass[2]));
return 0;
}
LDEBUGFS_SEQ_FOPS_RO(gss_proc_oos);
static ssize_t init_channel_store(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count)
{
int rc;
rc = gss_do_ctx_init_rpc((char *)buf, count);
if (rc) {
LASSERT(rc < 0);
return rc;
}
return count;
}
LUSTRE_WO_ATTR(init_channel);
static ssize_t
krb5_allow_old_client_csum_show(struct kobject *kobj,
struct attribute *attr, char *buf)
{
return scnprintf(buf, PAGE_SIZE, "%u\n", krb5_allow_old_client_csum);
}
static ssize_t
krb5_allow_old_client_csum_store(struct kobject *kobj,
struct attribute *attr,
const char *buf, size_t count)
{
bool val;
int rc;
rc = kstrtobool(buf, &val);
if (rc)
return rc;
krb5_allow_old_client_csum = val;
return count;
}
LUSTRE_RW_ATTR(krb5_allow_old_client_csum);
#ifdef HAVE_GSS_KEYRING
static ssize_t gss_check_upcall_ns_show(struct kobject *kobj,
struct attribute *attr, char *buf)
{
return scnprintf(buf, PAGE_SIZE, "%u\n", gss_check_upcall_ns);
}
static ssize_t gss_check_upcall_ns_store(struct kobject *kobj,
struct attribute *attr,
const char *buf, size_t count)
{
bool val;
int rc;
rc = kstrtobool(buf, &val);
if (rc)
return rc;
gss_check_upcall_ns = val;
return count;
}
LUSTRE_RW_ATTR(gss_check_upcall_ns);
#endif /* HAVE_GSS_KEYRING */
static ssize_t rsi_upcall_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
ssize_t len;
down_read(&rsicache->uc_upcall_rwsem);
len = scnprintf(buf, PAGE_SIZE, "%s\n", rsicache->uc_upcall);
up_read(&rsicache->uc_upcall_rwsem);
return len;
}
static ssize_t rsi_upcall_store(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count)
{
char *kbuf = NULL;
int rc;
OBD_ALLOC(kbuf, count + 1);
if (kbuf == NULL)
return -ENOMEM;
memcpy(kbuf, buf, count);
kbuf[count] = '\0';
rc = upcall_cache_set_upcall(rsicache, kbuf, count, true);
if (rc) {
CERROR("%s: incorrect rsi upcall %s. Valid value for sptlrpc.gss.rsi_upcall is an executable pathname: rc = %d\n",
rsicache->uc_name, kbuf, rc);
GOTO(out, rc);
}
CDEBUG(D_CONFIG, "%s: rsi upcall set to %s\n", rsicache->uc_name,
rsicache->uc_upcall);
rc = count;
out:
OBD_FREE(kbuf, count + 1);
return rc;
}
LUSTRE_RW_ATTR(rsi_upcall);
static ssize_t ldebugfs_rsi_info_seq_write(struct file *file,
const char __user *buffer,
size_t count, void *data)
{
struct rsi_downcall_data *param;
int size = sizeof(*param), rc, checked = 0;
again:
if (count < size) {
CERROR("%s: invalid data count = %lu, size = %d\n",
rsicache->uc_name, (unsigned long)count, size);
return -EINVAL;
}
OBD_ALLOC_LARGE(param, size);
if (param == NULL)
return -ENOMEM;
if (copy_from_user(param, buffer, size)) {
CERROR("%s: bad rsi data\n", rsicache->uc_name);
GOTO(out, rc = -EFAULT);
}
if (checked == 0) {
checked = 1;
if (param->sid_magic != RSI_DOWNCALL_MAGIC) {
CERROR("%s: rsi downcall bad params\n",
rsicache->uc_name);
GOTO(out, rc = -EINVAL);
}
rc = param->sid_len; /* save sid_len */
OBD_FREE_LARGE(param, size);
size = offsetof(struct rsi_downcall_data, sid_val[rc]);
goto again;
}
rc = upcall_cache_downcall(rsicache, param->sid_err,
param->sid_hash, param);
/* The caller, i.e. the userspace process writing to rsi_info, only
* needs to know about invalid values. Other errors are processed
* directly in the kernel.
*/
if (rc != -EINVAL)
rc = 0;
out:
if (param != NULL)
OBD_FREE_LARGE(param, size);
return rc ? rc : count;
}
LDEBUGFS_FOPS_WR_ONLY(gss, rsi_info);
static ssize_t rsi_entry_expire_show(struct kobject *kobj,
struct attribute *attr, char *buf)
{
return scnprintf(buf, PAGE_SIZE, "%lld\n", rsicache->uc_entry_expire);
}
static ssize_t rsi_entry_expire_store(struct kobject *kobj,
struct attribute *attr, const char *buf,
size_t count)
{
time64_t val;
int rc;
rc = kstrtoll(buf, 10, &val);
if (rc)
return rc;
if (val < 0)
return -ERANGE;
rsicache->uc_entry_expire = val;
return count;
}
LUSTRE_RW_ATTR(rsi_entry_expire);
static ssize_t rsi_acquire_expire_show(struct kobject *kobj,
struct attribute *attr, char *buf)
{
return scnprintf(buf, PAGE_SIZE, "%lld\n", rsicache->uc_acquire_expire);
}
static ssize_t rsi_acquire_expire_store(struct kobject *kobj,
struct attribute *attr,
const char *buf, size_t count)
{
time64_t val;
int rc;
rc = kstrtoll(buf, 10, &val);
if (rc)
return rc;
if (val < 0 || val > INT_MAX)
return -ERANGE;
rsicache->uc_acquire_expire = val;
return count;
}
LUSTRE_RW_ATTR(rsi_acquire_expire);
static ssize_t ldebugfs_rsc_info_seq_write(struct file *file,
const char __user *buffer,
size_t count, void *data)
{
struct rsc_downcall_data *param;
int size = sizeof(*param), rc, checked = 0;
struct gss_rsc rsc = { 0 }, *rscp = NULL;
char *mesg, *handle_buf;
again:
if (count < size) {
CERROR("%s: invalid data count = %lu, size = %d\n",
rsccache->uc_name, (unsigned long)count, size);
return -EINVAL;
}
OBD_ALLOC_LARGE(param, size);
if (param == NULL)
return -ENOMEM;
if (copy_from_user(param, buffer, size)) {
CERROR("%s: bad rsc data\n", rsccache->uc_name);
GOTO(out, rc = -EFAULT);
}
if (checked == 0) {
checked = 1;
if (param->scd_magic != RSC_DOWNCALL_MAGIC) {
CERROR("%s: rsc downcall bad params\n",
rsccache->uc_name);
GOTO(out, rc = -EINVAL);
}
rc = param->scd_len; /* save scd_len */
OBD_FREE_LARGE(param, size);
size = offsetof(struct rsc_downcall_data, scd_val[rc]);
goto again;
}
/* scd_val starts with handle.
* Use it to create cache entry.
*/
mesg = param->scd_val;
gss_u32_read(&mesg, &rsc.sc_handle.len);
if (!rsc.sc_handle.len) {
rc = -EINVAL;
goto out;
}
OBD_ALLOC_LARGE(handle_buf, rsc.sc_handle.len);
if (!handle_buf) {
rc = -ENOMEM;
goto out;
}
memset(handle_buf, 0, rsc.sc_handle.len);
mesg = param->scd_val;
rc = gss_buffer_read(&mesg, handle_buf, rsc.sc_handle.len);
if (rc < 0) {
OBD_FREE_LARGE(handle_buf, rsc.sc_handle.len);
rc = -EINVAL;
goto out;
}
rsc.sc_handle.data = handle_buf;
/* create cache entry on-the-fly */
rscp = rsc_entry_get(rsccache, &rsc);
__rsc_free(&rsc);
if (IS_ERR_OR_NULL(rscp)) {
if (IS_ERR(rscp))
rc = PTR_ERR(rscp);
else
rc = -EINVAL;
CERROR("%s: error in rsc_entry_get: rc = %d\n",
param->scd_mechname, rc);
goto out;
}
/* now that entry has been created, downcall can be done,
* but we have to tell acquiring is in progress
*/
upcall_cache_update_entry(rsccache, rscp->sc_uc_entry,
0, UC_CACHE_ACQUIRING);
rc = upcall_cache_downcall(rsccache, param->scd_err,
rscp->sc_uc_entry->ue_key, param);
out:
if (!IS_ERR_OR_NULL(rscp))
rsc_entry_put(rsccache, rscp);
if (param)
OBD_FREE_LARGE(param, size);
return rc ? rc : count;
}
LDEBUGFS_FOPS_WR_ONLY(gss, rsc_info);
static struct ldebugfs_vars gss_debugfs_vars[] = {
{ .name = "replays",
.fops = &gss_proc_oos_fops },
{ .name = "rsi_info",
.fops = &gss_rsi_info_fops },
{ .name = "rsc_info",
.fops = &gss_rsc_info_fops },
{ NULL }
};
/*
* for userspace helper lgss_keyring.
*
* debug_level: [0, 4], defined in utils/gss/lgss_utils.h
*/
static int gss_lk_debug_level = 1;
static ssize_t debug_level_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
return scnprintf(buf, PAGE_SIZE, "%u\n", gss_lk_debug_level);
}
static ssize_t debug_level_store(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count)
{
unsigned int val;
int rc;
rc = kstrtouint(buf, 0, &val);
if (rc < 0)
return rc;
if (val > 4)
return -ERANGE;
gss_lk_debug_level = val;
return count;
}
LUSTRE_RW_ATTR(debug_level);
static struct attribute *gss_attrs[] = {
&lustre_attr_init_channel.attr,
&lustre_attr_krb5_allow_old_client_csum.attr,
&lustre_attr_rsi_acquire_expire.attr,
&lustre_attr_rsi_entry_expire.attr,
&lustre_attr_rsi_upcall.attr,
#ifdef HAVE_GSS_KEYRING
&lustre_attr_gss_check_upcall_ns.attr,
#endif
NULL
};
static struct attribute_group gss_attr_group = {
.attrs = gss_attrs,
};
static struct attribute *gss_lk_attrs[] = {
&lustre_attr_debug_level.attr,
NULL
};
static struct attribute_group gss_lk_attr_group = {
.attrs = gss_lk_attrs,
};
void gss_exit_tunables(void)
{
if (gss_kobj_lk) {
sysfs_remove_group(gss_kobj_lk, &gss_lk_attr_group);
kobject_put(gss_kobj_lk);
}
if (gss_kobj) {
sysfs_remove_group(gss_kobj, &gss_attr_group);
kobject_put(gss_kobj);
}
debugfs_remove_recursive(gss_debugfs_dir);
gss_debugfs_dir = NULL;
}
int gss_init_tunables(void)
{
int rc;
spin_lock_init(&gss_stat_oos.oos_lock);
gss_debugfs_dir = debugfs_create_dir("gss", sptlrpc_debugfs_dir);
ldebugfs_add_vars(gss_debugfs_dir, gss_debugfs_vars, NULL);
gss_kobj = kobject_create_and_add("gss", sptlrpc_kobj);
if (!gss_kobj)
GOTO(out, rc = -ENOMEM);
rc = sysfs_create_group(gss_kobj, &gss_attr_group);
if (rc)
GOTO(out, rc);
gss_kobj_lk = kobject_create_and_add("lgss_keyring", gss_kobj);
if (!gss_kobj_lk)
GOTO(out, rc = -ENOMEM);
rc = sysfs_create_group(gss_kobj_lk, &gss_lk_attr_group);
if (rc)
GOTO(out, rc);
return 0;
out:
gss_exit_tunables();
return rc;
}