Sanitize vcoreid from untrusted sources
[akaros.git] / kern / src / ceq.c
1 /* Copyright (c) 2015 Google Inc.
2  * Barret Rhoden <brho@cs.berkeley.edu>
3  * See LICENSE for details.
4  *
5  * Coalescing Event Queue: encapuslates the essence of epoll/kqueue in shared
6  * memory: a dense array of sticky status bits.
7  *
8  * Kernel side (producer)
9  *
10  * All of the printks are just us helping the user debug their CEQs. */
11
12 #include <ceq.h>
13 #include <process.h>
14 #include <stdio.h>
15 #include <umem.h>
16
17 static void error_addr(struct ceq *ceq, struct proc *p, void *addr)
18 {
19         printk("[kernel] Invalid ceq (%p) bad addr %p for proc %d\n", ceq,
20                addr, p->pid);
21 }
22
23 static void ceq_update_max_event(struct ceq *ceq, unsigned int new_max)
24 {
25         unsigned int old_max;
26
27         do {
28                 old_max = atomic_read(&ceq->max_event_ever);
29                 if (new_max <= old_max)
30                         return;
31         } while (!atomic_cas(&ceq->max_event_ever, old_max, new_max));
32 }
33
34 void send_ceq_msg(struct ceq *ceq, struct proc *p, struct event_msg *msg)
35 {
36         struct ceq_event *ceq_ev;
37         int32_t *ring_slot;
38         unsigned long my_slot;
39         int loops = 0;
40         #define NR_RING_TRIES 10
41
42         /* should have been checked by the kernel func that called us */
43         assert(is_user_rwaddr(ceq, sizeof(struct ceq)));
44         if (msg->ev_type >= ceq->nr_events) {
45                 printk("[kernel] CEQ %p too small.  Wanted %d, had %d\n", ceq,
46                        msg->ev_type, ceq->nr_events);
47                 return;
48         }
49         ceq_update_max_event(ceq, msg->ev_type);
50         /* ACCESS_ONCE, prevent the compiler from rereading ceq->events later,
51          * and possibly getting a new, illegal version after our check */
52         ceq_ev = &(ACCESS_ONCE(ceq->events))[msg->ev_type];
53         if (!is_user_rwaddr(ceq_ev, sizeof(struct ceq_event))) {
54                 error_addr(ceq, p, ceq);
55                 return;
56         }
57         /* ideally, we'd like the blob to be posted after the coal, so that the
58          * 'reason' for the blob is present when the blob is.  but we can't
59          * guarantee that.  after we write the coal, the cons could consume
60          * that.  then the next time it looks at us, it could just see the blob
61          * - so there's no good way to keep them together.  the user will just
62          *   have to deal with it.  in that case, we might as well do it first,
63          *   to utilize the atomic ops's memory barrier. */
64         ceq_ev->blob_data = (uint64_t)msg->ev_arg3;
65         switch (ceq->operation) {
66         case (CEQ_OR):
67                 atomic_or(&ceq_ev->coalesce, msg->ev_arg2);
68                 break;
69         case (CEQ_ADD):
70                 atomic_add(&ceq_ev->coalesce, msg->ev_arg2);
71                 break;
72         default:
73                 printk("[kernel] CEQ %p invalid op %d\n", ceq, ceq->operation);
74                 return;
75         }
76         /* write before checking if we need to post (covered by the atomic) */
77         if (ceq_ev->idx_posted) {
78                 /* our entry was updated and posted was still set: we know the
79                  * consumer will still check it, so we can safely leave.  If we
80                  * ever have exit codes or something from send_*_msg, then we
81                  * can tell the kernel to not bother with INDIRS/IPIs/etc.  This
82                  * is unnecessary now since INDIRs are throttled */
83                 return;
84         }
85         /* at this point, we need to make sure the cons looks at our entry.  it
86          * may have already done so while we were mucking around, but 'poking'
87          * them to look again can't hurt */
88         ceq_ev->idx_posted = TRUE;
89         /* idx_posted write happens before the writes posting it.  the following
90          * atomic provides the cpu mb() */
91         cmb();
92         /* I considered checking the buffer for full-ness or the ceq overflow
93          * here.  Those would be reads, which would require a wrmb() right above
94          * for every ring post, all for something we check for later anyways and
95          * for something that should be rare.  In return, when we are
96          * overflowed, which should be rare if the user sizes their ring buffer
97          * appropriately, we go through a little more hassle below. */
98         /* I tried doing this with fetch_and_add to avoid the while loop and
99          * picking a number of times to try.  The trick is that you need to back
100          * out, and could have multiple producers working on the same slot.
101          * Although the overflow makes it okay for the producers idxes to be
102          * clobbered, it's not okay to have two producers on the same slot,
103          * since there'd only be one consumer.  Theoretically, you could have a
104          * producer delayed a long time that just clobbers an index at some
105          * point in the future, or leaves an index in the non-init state (-1).
106          * It's a mess. */
107         do {
108                 cmb();  /* reread the indices */
109                 my_slot = atomic_read(&ceq->prod_idx);
110                 if (__ring_full(ceq->ring_sz, my_slot,
111                                 atomic_read(&ceq->cons_pub_idx))) {
112                         ceq->ring_overflowed = TRUE;
113                         return;
114                 }
115                 if (loops++ == NR_RING_TRIES) {
116                         ceq->ring_overflowed = TRUE;
117                         return;
118                 }
119         } while (!atomic_cas(&ceq->prod_idx, my_slot, my_slot + 1));
120         /* ring_slot is a user pointer, calculated by ring, my_slot, and sz */
121         ring_slot = &(ACCESS_ONCE(ceq->ring))[my_slot & (ceq->ring_sz - 1)];
122         if (!is_user_rwaddr(ring_slot, sizeof(int32_t))) {
123                 /* This is a serious user error.  We're just bailing out, and
124                  * any consumers might be spinning waiting on us to produce.
125                  * Probably not though, since the ring slot is bad memory. */
126                 error_addr(ceq, p, ring_slot);
127                 return;
128         }
129         /* At this point, we have a valid slot */
130         *ring_slot = msg->ev_type;
131 }
132
133 void ceq_dumper(int pid, struct event_queue *ev_q)
134 {
135         struct proc *p;
136         uintptr_t switch_state;
137         struct ceq *ceq;
138
139         p = pid2proc(pid);
140         if (!p) {
141                 printk("No such proc %d\n", pid);
142                 return;
143         }
144         switch_state = switch_to(p);
145         if (ev_q->ev_mbox->type != EV_MBOX_CEQ) {
146                 printk("Not a CEQ evq (type %d)\n", ev_q->ev_mbox->type);
147                 goto out;
148         }
149         ceq = &ev_q->ev_mbox->ceq;
150         printk("CEQ %p\n---------------\n"
151                "\tevents ptr %p\n"
152                "\tnr_events %d\n"
153                "\tlast_recovered %d\n"
154                "\tmax_event_ever %ld\n"
155                "\tring %p\n"
156                "\tring_sz %d\n"
157                "\toperation %d\n"
158                "\tring_overflowed %d\n"
159                "\toverflow_recovery %d\n"
160                "\tprod_idx %lu\n"
161                "\tcons_pub_idx %lu\n"
162                "\tcons_pvt_idx %lu\n"
163                "\n",
164                    ceq,
165                ceq->events,
166                ceq->nr_events,
167                ceq->last_recovered,
168                atomic_read(&ceq->max_event_ever),
169                ceq->ring,
170                ceq->ring_sz,
171                ceq->operation,
172                ceq->ring_overflowed,
173                ceq->overflow_recovery,
174                atomic_read(&ceq->prod_idx),
175                atomic_read(&ceq->cons_pub_idx),
176                atomic_read(&ceq->cons_pvt_idx));
177         for (int i = 0; i < atomic_read(&ceq->max_event_ever) + 1; i++)
178                 printk("\tEvent%3d, coal %p, blob %p, idx_posted %d, user %p\n",
179                        i, atomic_read(&ceq->events[i].coalesce),
180                        ceq->events[i].blob_data,
181                        ceq->events[i].idx_posted,
182                        ceq->events[i].user_data);
183 out:
184         switch_back(p, switch_state);
185         proc_decref(p);
186 }