blob: 49f5fe830153914dfb7e323351dd0a39d95201df [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Container Security Monitor module
*
* Copyright (c) 2018 Google, Inc
*/
#include "monitor.h"
#include <linux/pipe_fs_i.h>
#include <linux/printk.h>
#include <linux/ratelimit.h>
#include <linux/uio.h>
#include <linux/workqueue.h>
/* csm protobuf work */
static void csm_sendmsg_pipe_handler(struct work_struct *work);
/* csm message work container */
struct msg_work_data {
struct work_struct msg_work;
size_t pos_bytes_written;
char msg[];
};
/* Mutex to ensure sequential dumping of protos */
static DEFINE_MUTEX(protodump);
static ssize_t csm_user_pipe_write(struct kvec *vecs, size_t vecs_size,
size_t total_length)
{
ssize_t perr = 0;
struct iov_iter io = { };
loff_t pos = 0;
struct pipe_inode_info *pipe;
unsigned int readers;
if (!csm_user_write_pipe)
return 0;
down_read(&csm_rwsem_pipe);
if (csm_user_write_pipe == NULL)
goto end;
/* The pipe info is the same for reader and write files. */
pipe = get_pipe_info(csm_user_write_pipe);
/* If nobody is listening, don't write events. */
readers = READ_ONCE(pipe->readers);
if (readers <= 1) {
WARN_ON(readers == 0);
goto end;
}
iov_iter_kvec(&io, ITER_KVEC|WRITE, vecs, vecs_size, total_length);
file_start_write(csm_user_write_pipe);
perr = vfs_iter_write(csm_user_write_pipe, &io, &pos, 0);
file_end_write(csm_user_write_pipe);
end:
up_read(&csm_rwsem_pipe);
return perr;
}
static int csm_sendmsg(int type, const void *buf, size_t len)
{
struct csm_msg_hdr hdr = {
.msg_type = cpu_to_le32(type),
.msg_length = cpu_to_le32(sizeof(hdr) + len),
};
struct kvec vecs[] = {
{
.iov_base = &hdr,
.iov_len = sizeof(hdr),
}, {
.iov_base = (void *)buf,
.iov_len = len,
}
};
ssize_t perr;
perr = csm_user_pipe_write(vecs, ARRAY_SIZE(vecs),
le32_to_cpu(hdr.msg_length));
if (perr < 0) {
pr_warn_ratelimited("vfs_iter_write error (msg_type=%d, msg_length=%u): %zd\n",
type, le32_to_cpu(hdr.msg_length), perr);
csm_stats.event_writing_failed++;
}
return perr;
}
static bool csm_get_expected_size(size_t *size, const pb_msgdesc_t *fields,
const void *src_struct)
{
schema_Event *event;
if (fields != schema_Event_fields)
goto other;
/* Size above 99% of the 100 containers tested running k8s. */
event = (schema_Event *)src_struct;
switch (event->which_event) {
case schema_Event_execute_tag:
*size = 3344;
return true;
case schema_Event_memexec_tag:
*size = 176;
return true;
case schema_Event_clone_tag:
*size = 50;
return true;
case schema_Event_exit_tag:
*size = 30;
return true;
}
other:
/* If unknown, do the pre-computation. */
return pb_get_encoded_size(size, fields, src_struct);
}
static struct msg_work_data *csm_encodeproto(size_t size,
const pb_msgdesc_t *fields,
const void *src_struct)
{
pb_ostream_t pos;
struct msg_work_data *wd;
size_t total;
total = size + sizeof(*wd);
if (total < size)
return ERR_PTR(-EINVAL);
wd = kmalloc(total, GFP_KERNEL);
if (!wd)
return ERR_PTR(-ENOMEM);
pos = pb_ostream_from_buffer(wd->msg, size);
if (!pb_encode(&pos, fields, src_struct)) {
kfree(wd);
return ERR_PTR(-EINVAL);
}
INIT_WORK(&wd->msg_work, csm_sendmsg_pipe_handler);
wd->pos_bytes_written = pos.bytes_written;
return wd;
}
static int csm_sendproto(int type, const pb_msgdesc_t *fields,
const void *src_struct)
{
int err = 0;
size_t size, previous_size;
struct msg_work_data *wd;
/* Use the expected size first. */
if (!csm_get_expected_size(&size, fields, src_struct))
return -EINVAL;
wd = csm_encodeproto(size, fields, src_struct);
if (IS_ERR(wd)) {
/* If it failed, retry with the exact size. */
csm_stats.size_picking_failed++;
previous_size = size;
if (!pb_get_encoded_size(&size, fields, src_struct))
return -EINVAL;
wd = csm_encodeproto(size, fields, src_struct);
if (IS_ERR(wd)) {
csm_stats.proto_encoding_failed++;
return PTR_ERR(wd);
}
pr_debug("size picking failed %lu vs %lu\n", previous_size,
size);
}
/* The work handler takes care of cleanup, if successfully scheduled. */
if (likely(schedule_work(&wd->msg_work)))
return 0;
csm_stats.workqueue_failed++;
pr_err_ratelimited("Sent msg to workqueue unsuccessfully (assume dropped).\n");
kfree(wd);
return err;
}
static void csm_sendmsg_pipe_handler(struct work_struct *work)
{
int err;
int type = CSM_MSG_EVENT_PROTO;
struct msg_work_data *wd = container_of(work, struct msg_work_data,
msg_work);
err = csm_sendmsg(type, wd->msg, wd->pos_bytes_written);
if (err < 0)
pr_err_ratelimited("csm_sendmsg failed in work handler %s\n",
__func__);
kfree(wd);
}
int csm_sendeventproto(const pb_msgdesc_t *fields, schema_Event *event)
{
/* Last check before generating and sending an event. */
if (!csm_enabled)
return -ENOTSUPP;
event->timestamp = ktime_get_real_ns();
return csm_sendproto(CSM_MSG_EVENT_PROTO, fields, event);
}