Viewing: l_getsepol.c
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2016 DDN Storage
* Author: Sebastien Buisson sbuisson@ddn.com
*/
/*
* lustre/utils/l_getsepol.c
* Userland helper to retrieve SELinux policy information.
*/
#include <sys/types.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <libgen.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
#include <syslog.h>
#include <stdarg.h>
#include <fcntl.h>
#include <stddef.h>
#include <ctype.h>
#include <dirent.h>
#include <getopt.h>
#include <assert.h>
#include <openssl/evp.h>
#include <selinux/selinux.h>
#include <libcfs/util/param.h>
#include <linux/lustre/lustre_user.h>
#include <linux/lustre/lustre_idl.h>
static char *progname;
static char *obd_type = NULL, *obd_name = NULL;
static time_t ref_pol_mtime = 0;
static char ref_selinux_mode = -1;
static void errlog(const char *fmt, ...)
{
va_list args;
openlog(progname, LOG_PID, LOG_AUTHPRIV);
va_start(args, fmt);
vsyslog(LOG_NOTICE, fmt, args);
if (isatty(STDIN_FILENO))
vfprintf(stderr, fmt, args);
va_end(args);
closelog();
}
/* Retrieve name of policy loaded, and version */
static int sepol_get_policy_info(char **policyname)
{
char *pol_path;
/* Name of loaded policy can be retrieved from policy root path */
pol_path = strdup(selinux_policy_root());
if (!pol_path) {
*policyname = NULL;
errlog("can't get policy name: %s\n", strerror(errno));
return -errno;
}
*policyname = strdup(basename(pol_path));
free(pol_path);
return 0;
}
/* Read binary SELinux policy, and compute hash */
static int sepol_get_policy_data(const char *pol_bin_path,
unsigned char **mdval, unsigned int *mdsize)
{
int fd;
char buffer[1024];
ssize_t count = 1024;
EVP_MD_CTX *mdctx;
const EVP_MD *md = EVP_sha256(); /* use SHA-256 */
int rc;
/* Open policy file */
fd = open(pol_bin_path, O_RDONLY);
if (fd < 0) {
errlog("can't open SELinux policy file %s: %s\n", pol_bin_path,
strerror(errno));
rc = -ENOENT;
goto out;
}
/* Read policy file */
mdctx = EVP_MD_CTX_create();
EVP_DigestInit_ex(mdctx, md, NULL);
while (count == 1024) {
count = read(fd, buffer, count);
if (count < 0) {
errlog("can't read SELinux policy file %s\n",
pol_bin_path);
rc = -errno;
close(fd);
goto out;
}
EVP_DigestUpdate(mdctx, buffer, count);
}
/* Close policy file */
rc = close(fd);
if (rc < 0) {
rc = -errno;
goto out;
}
*mdsize = EVP_MD_size(md);
*mdval = malloc(*mdsize);
if (*mdval == NULL) {
rc = -ENOMEM;
goto out;
}
EVP_DigestFinal_ex(mdctx, *mdval, NULL);
EVP_MD_CTX_destroy(mdctx);
out:
return rc;
}
static int get_opts(int argc, char *const argv[])
{
static struct option long_opts[] = {
{ .val = 'o', .name = "obd_type",
.has_arg = required_argument},
{ .val = 'n', .name = "obd_name",
.has_arg = required_argument},
{ .val = 't', .name = "sel_mtime",
.has_arg = required_argument},
{ .val = 'm', .name = "sel_mode",
.has_arg = required_argument},
{ .name = NULL } };
char *short_opts = "o:n:t:m:";
int opt;
int longidx;
char *sel_mtime = NULL, *sel_mode = NULL;
char *res;
optind = 0;
while ((opt = getopt_long(argc, argv, short_opts, long_opts,
&longidx)) != EOF) {
switch (opt) {
case 'o':
obd_type = optarg;
break;
case 'n':
obd_name = optarg;
break;
case 't':
sel_mtime = optarg;
break;
case 'm':
sel_mode = optarg;
break;
default:
if (opt != '?')
fprintf(stderr, "Unknown option '%c'\n", opt);
return -EINVAL;
}
}
if (optind != argc) {
errlog("incorrect arguments\n");
return -EINVAL;
}
if (!obd_type || !obd_name)
/* called without arg (presumably from command line):
* ignore everything */
return 0;
if (sel_mtime) {
ref_pol_mtime = (time_t)strtoul(sel_mtime, &res, 0);
if (*res != '\0') {
/* not a valid number */
errlog("invalid sel_mtime\n");
return -EINVAL;
}
}
if (sel_mode) {
ref_selinux_mode = sel_mode[0] - '0';
if (ref_selinux_mode != 0 && ref_selinux_mode != 1) {
/* not a valid enforcing mode */
errlog("invalid sel_mode\n");
return -EINVAL;
}
}
return 0;
}
#define sepol_downcall(type_t, magic) ({ \
glob_t path; \
int fd, size; \
struct type_t *data; \
int idx; \
char *p; \
\
size = offsetof(struct type_t, \
sdd_sepol[LUSTRE_NODEMAP_SEPOL_LENGTH + 1]); \
data = malloc(size); \
if (!data) { \
errlog("malloc sepol downcall data(%d) failed!\n", size); \
rc = -ENOMEM; \
goto out_mdval; \
} \
memset(data, 0, size); \
\
/* Put all info together and generate string \
* to represent SELinux policy information \
*/ \
rc = snprintf(data->sdd_sepol, LUSTRE_NODEMAP_SEPOL_LENGTH + 1, \
"%.1d:%s:%u:", enforce, policy_type, policyver); \
if (rc >= LUSTRE_NODEMAP_SEPOL_LENGTH + 1) { \
rc = -EMSGSIZE; \
goto out_data_ ## type_t ; \
} \
\
p = data->sdd_sepol + strlen(data->sdd_sepol); \
size = LUSTRE_NODEMAP_SEPOL_LENGTH + 1 - strlen(data->sdd_sepol); \
for (idx = 0; idx < mdsize; idx++) { \
rc = snprintf(p, size, "%02x", \
(unsigned char)(mdval[idx])); \
p += 2; \
size -= 2; \
if (size < 0 || rc >= size) { \
rc = -EMSGSIZE; \
goto out_data_ ## type_t ; \
} \
} \
data->sdd_sepol_len = p - data->sdd_sepol; \
\
size = offsetof(struct type_t, \
sdd_sepol[data->sdd_sepol_len]); \
\
if (!obd_type || !obd_name) { \
/* called without arg (presumably from command line): \
* print SELinux status and exit \
*/ \
printf("SELinux status info: %.*s\n", \
data->sdd_sepol_len, data->sdd_sepol); \
return 0; \
} \
\
data->sdd_magic = magic; \
data->sdd_sepol_mtime = policymtime; \
/* Send SELinux policy info to kernelspace */ \
rc = cfs_get_param_paths(&path, "%s/%s/srpc_sepol", obd_type, \
obd_name); \
if (rc != 0) { \
errlog("can't get param '%s/%s/srpc_sepol': %s\n", \
obd_type, obd_name, strerror(errno)); \
rc = -errno; \
goto out_data_ ## type_t ; \
} \
\
fd = open(path.gl_pathv[0], O_WRONLY); \
if (fd < 0) { \
errlog("can't open file '%s':%s\n", path.gl_pathv[0], \
strerror(errno)); \
rc = -errno; \
goto out_params_ ## type_t ; \
} \
\
rc = write(fd, data, size); \
close(fd); \
if (rc != size) { \
errlog("partial write ret %d: %s\n", rc, strerror(errno)); \
rc = -errno; \
} else { \
rc = 0; \
} \
\
out_params_ ## type_t : \
cfs_free_param_data(&path); \
out_data_ ## type_t : \
free(data); \
})
/* Find policy with highest version */
static int find_policy_with_highest_ver(const char *base_pol_path,
char *pol_bin_path,
size_t pol_bin_path_len,
int *policyver,
time_t *policymtime)
{
char *policy_dir = NULL;
size_t policy_dir_len;
char *policy_file = NULL;
size_t policy_file_len;
char pol_bin_file[PATH_MAX + 1];
DIR *dp = NULL;
struct dirent *de;
struct stat st;
char *name, *end, *p;
char max_ver_name[PATH_MAX + 1];
int ver, max_ver = 0;
time_t max_ver_pol_mtime = 0;
/* Get dir of selinux_binary_policy_path. */
policy_dir = dirname(pol_bin_path);
policy_dir_len = strlen(policy_dir);
/*
* Get filename of selinux_binary_policy_path.
* Use snprintf instead of strncpy to avoid a
* -Werror=stringop-truncation compilation warning.
*/
snprintf(pol_bin_file, sizeof(pol_bin_file), "%s",
base_pol_path);
policy_file = basename(pol_bin_file);
policy_file_len = strlen(policy_file);
/* We append '.' to policy_file. Now it's like 'policy.'. */
policy_file[policy_file_len] = '.';
policy_file_len++;
policy_file[policy_file_len] = '\0';
/* Open policy_dir. */
dp = opendir(policy_dir);
if (!dp) {
errlog("failed to open %s: %s\n", policy_dir, strerror(errno));
return -errno;
}
/* Scan policy_dir. */
while ((de = readdir(dp)) != NULL) {
name = de->d_name;
/* Skip entries that don't match the policy file prefix. */
if (strncmp(name, policy_file, policy_file_len) != 0)
continue;
/* Found, get version. */
p = name + policy_file_len;
ver = (int)strtol(p, &end, 10);
/* Skip files not ending with '.NN' (e.g. backups) */
if (end == p || *end != '\0')
continue;
if (ver > max_ver) {
if (fstatat(dirfd(dp), name, &st, 0) != 0) {
errlog("failed to stat %s: %s\n",
name, strerror(errno));
continue;
}
/* Update max_ver, etc. */
max_ver = ver;
max_ver_pol_mtime = st.st_mtime;
snprintf(max_ver_name, sizeof(max_ver_name),
"%s", name); /* Avoid compile warn */
}
}
closedir(dp);
if (max_ver == 0) {
errlog("can't stat %s.*: %s\n",
selinux_binary_policy_path(), strerror(errno));
return -ENOENT;
}
snprintf(pol_bin_path + policy_dir_len,
pol_bin_path_len - policy_dir_len, "/%s", max_ver_name);
*policyver = max_ver;
*policymtime = max_ver_pol_mtime;
return 0;
}
/*
* Calculate SELinux status information.
* String that represents SELinux status info has the following format:
* <mode>:<policy name>:<policy version>:<policy hash>
* <mode> is a digit equal to 0 for SELinux Permissive mode,
* and 1 for Enforcing mode.
* When called from kernel space, it requires 4 args:
* - obd type
* - obd name
* - SELinux policy mtime
* - SELinux enforcing mode
* When called from command line (in this case without proper args), it prints
* SELinux status info to stdout.
*/
int main(int argc, char **argv)
{
int policyver = 0;
char pol_bin_path[PATH_MAX + 1];
time_t policymtime = 0;
struct stat st;
int enforce;
int is_selinux;
char *policy_type = NULL;
unsigned char *mdval = NULL;
unsigned int mdsize = 0;
int rc;
progname = basename(argv[0]);
rc = get_opts(argc, argv);
if (rc < 0)
goto out;
is_selinux = is_selinux_enabled();
if (is_selinux < 0) {
errlog("is_selinux_enabled() failed\n");
rc = -errno;
goto out;
}
if (!is_selinux) {
errlog("SELinux is disabled, ptlrpc 'send_sepol' value should be set to 0\n");
rc = -ENODEV;
goto out;
}
/* Policy format version reported by the running kernel */
policyver = security_policyvers();
if (policyver < 0) {
errlog("unknown policy version: %s\n", strerror(errno));
rc = -errno;
goto out;
}
/* Determine if SELinux is in permissive or enforcing mode */
enforce = security_getenforce();
if (enforce < 0) {
errlog("can't getenforce: %s\n", strerror(errno));
rc = -errno;
goto out;
}
/* Find the on-disk policy.N file for both mtime (change detection)
* and hashing. Always using the on-disk file ensures all l_getsepol
* clients in a nodemap produce an identical hash string.
* Note: /sys/fs/selinux/policy also exports the loaded policy binary,
* but it is a re-serialization of the kernel's in-memory policydb,
* not a verbatim copy of the file, so its hash may differ.
*
* Search security_policyvers() first, then fall back to the highest
* version found on disk, to handle distros where the on-disk policy
* version exceeds what security_policyvers() reports (e.g. RHEL/Rocky
* 10.1: policy.35 on disk, security_policyvers()=33).
*/
const char *base_pol_path = selinux_binary_policy_path();
/* First, search policy with security_policyvers(). */
snprintf(pol_bin_path, sizeof(pol_bin_path), "%s.%d",
base_pol_path, policyver);
if (stat(pol_bin_path, &st) == 0) {
policymtime = st.st_mtime;
} else {
/* Not found. Search for policy file with highest version. */
rc = find_policy_with_highest_ver(base_pol_path, pol_bin_path,
sizeof(pol_bin_path),
&policyver, &policymtime);
if (rc < 0)
goto out;
}
if (ref_pol_mtime == policymtime && ref_selinux_mode == enforce) {
/* Policy has not changed: return immediately */
rc = 0;
goto out;
}
/* Now we need to calculate SELinux status information */
/* Get policy name */
rc = sepol_get_policy_info(&policy_type);
if (rc < 0)
goto out;
/* Read binary SELinux policy, and compute hash */
rc = sepol_get_policy_data(pol_bin_path, &mdval, &mdsize);
if (rc < 0)
goto out_poltyp;
sepol_downcall(sepol_downcall_data, SEPOL_DOWNCALL_MAGIC);
#if LUSTRE_VERSION_CODE < OBD_OCD_VERSION(2, 16, 53, 0)
if (rc == -EINVAL)
/* try with old magic */
sepol_downcall(sepol_downcall_data_old,
SEPOL_DOWNCALL_MAGIC_OLD);
#endif
out_mdval:
free(mdval);
out_poltyp:
free(policy_type);
out:
if (isatty(STDIN_FILENO))
/* we are called from the command line */
return rc < 0 ? -rc : rc;
else
return rc;
}