1 /******************************************************************************
2  *
3  * hypfs.c
4  *
5  * Simple sysfs-like file system for the hypervisor.
6  */
7 
8 #include <xen/err.h>
9 #include <xen/guest_access.h>
10 #include <xen/hypercall.h>
11 #include <xen/hypfs.h>
12 #include <xen/lib.h>
13 #include <xen/param.h>
14 #include <xen/rwlock.h>
15 #include <public/hypfs.h>
16 
17 #ifdef CONFIG_COMPAT
18 #include <compat/hypfs.h>
19 CHECK_hypfs_dirlistentry;
20 #endif
21 
22 #define DIRENTRY_NAME_OFF offsetof(struct xen_hypfs_dirlistentry, name)
23 #define DIRENTRY_SIZE(name_len) \
24     (DIRENTRY_NAME_OFF +        \
25      ROUNDUP((name_len) + 1, alignof(struct xen_hypfs_direntry)))
26 
27 static DEFINE_RWLOCK(hypfs_lock);
28 enum hypfs_lock_state {
29     hypfs_unlocked,
30     hypfs_read_locked,
31     hypfs_write_locked
32 };
33 static DEFINE_PER_CPU(enum hypfs_lock_state, hypfs_locked);
34 
35 HYPFS_DIR_INIT(hypfs_root, "");
36 
hypfs_read_lock(void)37 static void hypfs_read_lock(void)
38 {
39     ASSERT(this_cpu(hypfs_locked) != hypfs_write_locked);
40 
41     read_lock(&hypfs_lock);
42     this_cpu(hypfs_locked) = hypfs_read_locked;
43 }
44 
hypfs_write_lock(void)45 static void hypfs_write_lock(void)
46 {
47     ASSERT(this_cpu(hypfs_locked) == hypfs_unlocked);
48 
49     write_lock(&hypfs_lock);
50     this_cpu(hypfs_locked) = hypfs_write_locked;
51 }
52 
hypfs_unlock(void)53 static void hypfs_unlock(void)
54 {
55     enum hypfs_lock_state locked = this_cpu(hypfs_locked);
56 
57     this_cpu(hypfs_locked) = hypfs_unlocked;
58 
59     switch ( locked )
60     {
61     case hypfs_read_locked:
62         read_unlock(&hypfs_lock);
63         break;
64     case hypfs_write_locked:
65         write_unlock(&hypfs_lock);
66         break;
67     default:
68         BUG();
69     }
70 }
71 
add_entry(struct hypfs_entry_dir * parent,struct hypfs_entry * new)72 static int add_entry(struct hypfs_entry_dir *parent, struct hypfs_entry *new)
73 {
74     int ret = -ENOENT;
75     struct hypfs_entry *e;
76 
77     hypfs_write_lock();
78 
79     list_for_each_entry ( e, &parent->dirlist, list )
80     {
81         int cmp = strcmp(e->name, new->name);
82 
83         if ( cmp > 0 )
84         {
85             ret = 0;
86             list_add_tail(&new->list, &e->list);
87             break;
88         }
89         if ( cmp == 0 )
90         {
91             ret = -EEXIST;
92             break;
93         }
94     }
95 
96     if ( ret == -ENOENT )
97     {
98         ret = 0;
99         list_add_tail(&new->list, &parent->dirlist);
100     }
101 
102     if ( !ret )
103     {
104         unsigned int sz = strlen(new->name);
105 
106         parent->e.size += DIRENTRY_SIZE(sz);
107     }
108 
109     hypfs_unlock();
110 
111     return ret;
112 }
113 
hypfs_add_dir(struct hypfs_entry_dir * parent,struct hypfs_entry_dir * dir,bool nofault)114 int hypfs_add_dir(struct hypfs_entry_dir *parent,
115                   struct hypfs_entry_dir *dir, bool nofault)
116 {
117     int ret;
118 
119     ret = add_entry(parent, &dir->e);
120     BUG_ON(nofault && ret);
121 
122     return ret;
123 }
124 
hypfs_add_leaf(struct hypfs_entry_dir * parent,struct hypfs_entry_leaf * leaf,bool nofault)125 int hypfs_add_leaf(struct hypfs_entry_dir *parent,
126                    struct hypfs_entry_leaf *leaf, bool nofault)
127 {
128     int ret;
129 
130     if ( !leaf->u.content )
131         ret = -EINVAL;
132     else
133         ret = add_entry(parent, &leaf->e);
134     BUG_ON(nofault && ret);
135 
136     return ret;
137 }
138 
hypfs_get_path_user(char * buf,XEN_GUEST_HANDLE_PARAM (const_char)uaddr,unsigned long ulen)139 static int hypfs_get_path_user(char *buf,
140                                XEN_GUEST_HANDLE_PARAM(const_char) uaddr,
141                                unsigned long ulen)
142 {
143     if ( ulen > XEN_HYPFS_MAX_PATHLEN )
144         return -EINVAL;
145 
146     if ( copy_from_guest(buf, uaddr, ulen) )
147         return -EFAULT;
148 
149     if ( memchr(buf, 0, ulen) != buf + ulen - 1 )
150         return -EINVAL;
151 
152     return 0;
153 }
154 
hypfs_get_entry_rel(struct hypfs_entry_dir * dir,const char * path)155 static struct hypfs_entry *hypfs_get_entry_rel(struct hypfs_entry_dir *dir,
156                                                const char *path)
157 {
158     const char *end;
159     struct hypfs_entry *entry;
160     unsigned int name_len;
161     bool again = true;
162 
163     while ( again )
164     {
165         if ( dir->e.type != XEN_HYPFS_TYPE_DIR )
166             return NULL;
167 
168         if ( !*path )
169             return &dir->e;
170 
171         end = strchr(path, '/');
172         if ( !end )
173             end = strchr(path, '\0');
174         name_len = end - path;
175 
176         again = false;
177 
178         list_for_each_entry ( entry, &dir->dirlist, list )
179         {
180             int cmp = strncmp(path, entry->name, name_len);
181             struct hypfs_entry_dir *d = container_of(entry,
182                                                      struct hypfs_entry_dir, e);
183 
184             if ( cmp < 0 )
185                 return NULL;
186             if ( !cmp && strlen(entry->name) == name_len )
187             {
188                 if ( !*end )
189                     return entry;
190 
191                 again = true;
192                 dir = d;
193                 path = end + 1;
194 
195                 break;
196             }
197         }
198     }
199 
200     return NULL;
201 }
202 
hypfs_get_entry(const char * path)203 static struct hypfs_entry *hypfs_get_entry(const char *path)
204 {
205     if ( path[0] != '/' )
206         return NULL;
207 
208     return hypfs_get_entry_rel(&hypfs_root, path + 1);
209 }
210 
hypfs_read_dir(const struct hypfs_entry * entry,XEN_GUEST_HANDLE_PARAM (void)uaddr)211 int hypfs_read_dir(const struct hypfs_entry *entry,
212                    XEN_GUEST_HANDLE_PARAM(void) uaddr)
213 {
214     const struct hypfs_entry_dir *d;
215     const struct hypfs_entry *e;
216     unsigned int size = entry->size;
217 
218     ASSERT(this_cpu(hypfs_locked) != hypfs_unlocked);
219 
220     d = container_of(entry, const struct hypfs_entry_dir, e);
221 
222     list_for_each_entry ( e, &d->dirlist, list )
223     {
224         struct xen_hypfs_dirlistentry direntry;
225         unsigned int e_namelen = strlen(e->name);
226         unsigned int e_len = DIRENTRY_SIZE(e_namelen);
227 
228         direntry.e.pad = 0;
229         direntry.e.type = e->type;
230         direntry.e.encoding = e->encoding;
231         direntry.e.content_len = e->size;
232         direntry.e.max_write_len = e->max_size;
233         direntry.off_next = list_is_last(&e->list, &d->dirlist) ? 0 : e_len;
234         if ( copy_to_guest(uaddr, &direntry, 1) )
235             return -EFAULT;
236 
237         if ( copy_to_guest_offset(uaddr, DIRENTRY_NAME_OFF,
238                                   e->name, e_namelen + 1) )
239             return -EFAULT;
240 
241         guest_handle_add_offset(uaddr, e_len);
242 
243         ASSERT(e_len <= size);
244         size -= e_len;
245     }
246 
247     return 0;
248 }
249 
hypfs_read_leaf(const struct hypfs_entry * entry,XEN_GUEST_HANDLE_PARAM (void)uaddr)250 int hypfs_read_leaf(const struct hypfs_entry *entry,
251                     XEN_GUEST_HANDLE_PARAM(void) uaddr)
252 {
253     const struct hypfs_entry_leaf *l;
254 
255     ASSERT(this_cpu(hypfs_locked) != hypfs_unlocked);
256 
257     l = container_of(entry, const struct hypfs_entry_leaf, e);
258 
259     return copy_to_guest(uaddr, l->u.content, entry->size) ? -EFAULT: 0;
260 }
261 
hypfs_read(const struct hypfs_entry * entry,XEN_GUEST_HANDLE_PARAM (void)uaddr,unsigned long ulen)262 static int hypfs_read(const struct hypfs_entry *entry,
263                       XEN_GUEST_HANDLE_PARAM(void) uaddr, unsigned long ulen)
264 {
265     struct xen_hypfs_direntry e;
266     long ret = -EINVAL;
267 
268     if ( ulen < sizeof(e) )
269         goto out;
270 
271     e.pad = 0;
272     e.type = entry->type;
273     e.encoding = entry->encoding;
274     e.content_len = entry->size;
275     e.max_write_len = entry->max_size;
276 
277     ret = -EFAULT;
278     if ( copy_to_guest(uaddr, &e, 1) )
279         goto out;
280 
281     ret = -ENOBUFS;
282     if ( ulen < entry->size + sizeof(e) )
283         goto out;
284 
285     guest_handle_add_offset(uaddr, sizeof(e));
286 
287     ret = entry->read(entry, uaddr);
288 
289  out:
290     return ret;
291 }
292 
hypfs_write_leaf(struct hypfs_entry_leaf * leaf,XEN_GUEST_HANDLE_PARAM (void)uaddr,unsigned int ulen)293 int hypfs_write_leaf(struct hypfs_entry_leaf *leaf,
294                      XEN_GUEST_HANDLE_PARAM(void) uaddr, unsigned int ulen)
295 {
296     char *buf;
297     int ret;
298 
299     ASSERT(this_cpu(hypfs_locked) == hypfs_write_locked);
300 
301     if ( ulen > leaf->e.max_size )
302         return -ENOSPC;
303 
304     if ( leaf->e.type != XEN_HYPFS_TYPE_STRING &&
305          leaf->e.type != XEN_HYPFS_TYPE_BLOB && ulen != leaf->e.size )
306         return -EDOM;
307 
308     buf = xmalloc_array(char, ulen);
309     if ( !buf )
310         return -ENOMEM;
311 
312     ret = -EFAULT;
313     if ( copy_from_guest(buf, uaddr, ulen) )
314         goto out;
315 
316     ret = -EINVAL;
317     if ( leaf->e.type == XEN_HYPFS_TYPE_STRING &&
318          leaf->e.encoding == XEN_HYPFS_ENC_PLAIN &&
319          memchr(buf, 0, ulen) != (buf + ulen - 1) )
320         goto out;
321 
322     ret = 0;
323     memcpy(leaf->u.write_ptr, buf, ulen);
324     leaf->e.size = ulen;
325 
326  out:
327     xfree(buf);
328     return ret;
329 }
330 
hypfs_write_bool(struct hypfs_entry_leaf * leaf,XEN_GUEST_HANDLE_PARAM (void)uaddr,unsigned int ulen)331 int hypfs_write_bool(struct hypfs_entry_leaf *leaf,
332                      XEN_GUEST_HANDLE_PARAM(void) uaddr, unsigned int ulen)
333 {
334     bool buf;
335 
336     ASSERT(this_cpu(hypfs_locked) == hypfs_write_locked);
337     ASSERT(leaf->e.type == XEN_HYPFS_TYPE_BOOL &&
338            leaf->e.size == sizeof(bool) &&
339            leaf->e.max_size == sizeof(bool) );
340 
341     if ( ulen != leaf->e.max_size )
342         return -EDOM;
343 
344     if ( copy_from_guest(&buf, uaddr, ulen) )
345         return -EFAULT;
346 
347     *(bool *)leaf->u.write_ptr = buf;
348 
349     return 0;
350 }
351 
hypfs_write_custom(struct hypfs_entry_leaf * leaf,XEN_GUEST_HANDLE_PARAM (void)uaddr,unsigned int ulen)352 int hypfs_write_custom(struct hypfs_entry_leaf *leaf,
353                        XEN_GUEST_HANDLE_PARAM(void) uaddr, unsigned int ulen)
354 {
355     struct param_hypfs *p;
356     char *buf;
357     int ret;
358 
359     ASSERT(this_cpu(hypfs_locked) == hypfs_write_locked);
360 
361     /* Avoid oversized buffer allocation. */
362     if ( ulen > MAX_PARAM_SIZE )
363         return -ENOSPC;
364 
365     buf = xzalloc_array(char, ulen);
366     if ( !buf )
367         return -ENOMEM;
368 
369     ret = -EFAULT;
370     if ( copy_from_guest(buf, uaddr, ulen) )
371         goto out;
372 
373     ret = -EDOM;
374     if ( memchr(buf, 0, ulen) != (buf + ulen - 1) )
375         goto out;
376 
377     p = container_of(leaf, struct param_hypfs, hypfs);
378     ret = p->func(buf);
379 
380  out:
381     xfree(buf);
382     return ret;
383 }
384 
hypfs_write(struct hypfs_entry * entry,XEN_GUEST_HANDLE_PARAM (void)uaddr,unsigned long ulen)385 static int hypfs_write(struct hypfs_entry *entry,
386                        XEN_GUEST_HANDLE_PARAM(void) uaddr, unsigned long ulen)
387 {
388     struct hypfs_entry_leaf *l;
389 
390     if ( !entry->write )
391         return -EACCES;
392 
393     ASSERT(entry->max_size);
394 
395     l = container_of(entry, struct hypfs_entry_leaf, e);
396 
397     return entry->write(l, uaddr, ulen);
398 }
399 
do_hypfs_op(unsigned int cmd,XEN_GUEST_HANDLE_PARAM (const_char)arg1,unsigned long arg2,XEN_GUEST_HANDLE_PARAM (void)arg3,unsigned long arg4)400 long do_hypfs_op(unsigned int cmd,
401                  XEN_GUEST_HANDLE_PARAM(const_char) arg1, unsigned long arg2,
402                  XEN_GUEST_HANDLE_PARAM(void) arg3, unsigned long arg4)
403 {
404     int ret;
405     struct hypfs_entry *entry;
406     static char path[XEN_HYPFS_MAX_PATHLEN];
407 
408     if ( xsm_hypfs_op(XSM_PRIV) )
409         return -EPERM;
410 
411     if ( cmd == XEN_HYPFS_OP_get_version )
412     {
413         if ( !guest_handle_is_null(arg1) || arg2 ||
414              !guest_handle_is_null(arg3) || arg4 )
415             return -EINVAL;
416 
417         return XEN_HYPFS_VERSION;
418     }
419 
420     if ( cmd == XEN_HYPFS_OP_write_contents )
421         hypfs_write_lock();
422     else
423         hypfs_read_lock();
424 
425     ret = hypfs_get_path_user(path, arg1, arg2);
426     if ( ret )
427         goto out;
428 
429     entry = hypfs_get_entry(path);
430     if ( !entry )
431     {
432         ret = -ENOENT;
433         goto out;
434     }
435 
436     switch ( cmd )
437     {
438     case XEN_HYPFS_OP_read:
439         ret = hypfs_read(entry, arg3, arg4);
440         break;
441 
442     case XEN_HYPFS_OP_write_contents:
443         ret = hypfs_write(entry, arg3, arg4);
444         break;
445 
446     default:
447         ret = -EOPNOTSUPP;
448         break;
449     }
450 
451  out:
452     hypfs_unlock();
453 
454     return ret;
455 }
456