| // 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, 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); |
| } |