18c2ecf20Sopenharmony_ci/* 28c2ecf20Sopenharmony_ci * memfd_create system call and file sealing support 38c2ecf20Sopenharmony_ci * 48c2ecf20Sopenharmony_ci * Code was originally included in shmem.c, and broken out to facilitate 58c2ecf20Sopenharmony_ci * use by hugetlbfs as well as tmpfs. 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * This file is released under the GPL. 88c2ecf20Sopenharmony_ci */ 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include <linux/fs.h> 118c2ecf20Sopenharmony_ci#include <linux/vfs.h> 128c2ecf20Sopenharmony_ci#include <linux/pagemap.h> 138c2ecf20Sopenharmony_ci#include <linux/file.h> 148c2ecf20Sopenharmony_ci#include <linux/mm.h> 158c2ecf20Sopenharmony_ci#include <linux/sched/signal.h> 168c2ecf20Sopenharmony_ci#include <linux/khugepaged.h> 178c2ecf20Sopenharmony_ci#include <linux/syscalls.h> 188c2ecf20Sopenharmony_ci#include <linux/hugetlb.h> 198c2ecf20Sopenharmony_ci#include <linux/shmem_fs.h> 208c2ecf20Sopenharmony_ci#include <linux/memfd.h> 218c2ecf20Sopenharmony_ci#include <uapi/linux/memfd.h> 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci/* 248c2ecf20Sopenharmony_ci * We need a tag: a new tag would expand every xa_node by 8 bytes, 258c2ecf20Sopenharmony_ci * so reuse a tag which we firmly believe is never set or cleared on tmpfs 268c2ecf20Sopenharmony_ci * or hugetlbfs because they are memory only filesystems. 278c2ecf20Sopenharmony_ci */ 288c2ecf20Sopenharmony_ci#define MEMFD_TAG_PINNED PAGECACHE_TAG_TOWRITE 298c2ecf20Sopenharmony_ci#define LAST_SCAN 4 /* about 150ms max */ 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_cistatic void memfd_tag_pins(struct xa_state *xas) 328c2ecf20Sopenharmony_ci{ 338c2ecf20Sopenharmony_ci struct page *page; 348c2ecf20Sopenharmony_ci int latency = 0; 358c2ecf20Sopenharmony_ci int cache_count; 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci lru_add_drain(); 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci xas_lock_irq(xas); 408c2ecf20Sopenharmony_ci xas_for_each(xas, page, ULONG_MAX) { 418c2ecf20Sopenharmony_ci cache_count = 1; 428c2ecf20Sopenharmony_ci if (!xa_is_value(page) && 438c2ecf20Sopenharmony_ci PageTransHuge(page) && !PageHuge(page)) 448c2ecf20Sopenharmony_ci cache_count = HPAGE_PMD_NR; 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci if (!xa_is_value(page) && 478c2ecf20Sopenharmony_ci page_count(page) - total_mapcount(page) != cache_count) 488c2ecf20Sopenharmony_ci xas_set_mark(xas, MEMFD_TAG_PINNED); 498c2ecf20Sopenharmony_ci if (cache_count != 1) 508c2ecf20Sopenharmony_ci xas_set(xas, page->index + cache_count); 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci latency += cache_count; 538c2ecf20Sopenharmony_ci if (latency < XA_CHECK_SCHED) 548c2ecf20Sopenharmony_ci continue; 558c2ecf20Sopenharmony_ci latency = 0; 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci xas_pause(xas); 588c2ecf20Sopenharmony_ci xas_unlock_irq(xas); 598c2ecf20Sopenharmony_ci cond_resched(); 608c2ecf20Sopenharmony_ci xas_lock_irq(xas); 618c2ecf20Sopenharmony_ci } 628c2ecf20Sopenharmony_ci xas_unlock_irq(xas); 638c2ecf20Sopenharmony_ci} 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci/* 668c2ecf20Sopenharmony_ci * Setting SEAL_WRITE requires us to verify there's no pending writer. However, 678c2ecf20Sopenharmony_ci * via get_user_pages(), drivers might have some pending I/O without any active 688c2ecf20Sopenharmony_ci * user-space mappings (eg., direct-IO, AIO). Therefore, we look at all pages 698c2ecf20Sopenharmony_ci * and see whether it has an elevated ref-count. If so, we tag them and wait for 708c2ecf20Sopenharmony_ci * them to be dropped. 718c2ecf20Sopenharmony_ci * The caller must guarantee that no new user will acquire writable references 728c2ecf20Sopenharmony_ci * to those pages to avoid races. 738c2ecf20Sopenharmony_ci */ 748c2ecf20Sopenharmony_cistatic int memfd_wait_for_pins(struct address_space *mapping) 758c2ecf20Sopenharmony_ci{ 768c2ecf20Sopenharmony_ci XA_STATE(xas, &mapping->i_pages, 0); 778c2ecf20Sopenharmony_ci struct page *page; 788c2ecf20Sopenharmony_ci int error, scan; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci memfd_tag_pins(&xas); 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci error = 0; 838c2ecf20Sopenharmony_ci for (scan = 0; scan <= LAST_SCAN; scan++) { 848c2ecf20Sopenharmony_ci int latency = 0; 858c2ecf20Sopenharmony_ci int cache_count; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci if (!xas_marked(&xas, MEMFD_TAG_PINNED)) 888c2ecf20Sopenharmony_ci break; 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci if (!scan) 918c2ecf20Sopenharmony_ci lru_add_drain_all(); 928c2ecf20Sopenharmony_ci else if (schedule_timeout_killable((HZ << scan) / 200)) 938c2ecf20Sopenharmony_ci scan = LAST_SCAN; 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci xas_set(&xas, 0); 968c2ecf20Sopenharmony_ci xas_lock_irq(&xas); 978c2ecf20Sopenharmony_ci xas_for_each_marked(&xas, page, ULONG_MAX, MEMFD_TAG_PINNED) { 988c2ecf20Sopenharmony_ci bool clear = true; 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci cache_count = 1; 1018c2ecf20Sopenharmony_ci if (!xa_is_value(page) && 1028c2ecf20Sopenharmony_ci PageTransHuge(page) && !PageHuge(page)) 1038c2ecf20Sopenharmony_ci cache_count = HPAGE_PMD_NR; 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci if (!xa_is_value(page) && cache_count != 1068c2ecf20Sopenharmony_ci page_count(page) - total_mapcount(page)) { 1078c2ecf20Sopenharmony_ci /* 1088c2ecf20Sopenharmony_ci * On the last scan, we clean up all those tags 1098c2ecf20Sopenharmony_ci * we inserted; but make a note that we still 1108c2ecf20Sopenharmony_ci * found pages pinned. 1118c2ecf20Sopenharmony_ci */ 1128c2ecf20Sopenharmony_ci if (scan == LAST_SCAN) 1138c2ecf20Sopenharmony_ci error = -EBUSY; 1148c2ecf20Sopenharmony_ci else 1158c2ecf20Sopenharmony_ci clear = false; 1168c2ecf20Sopenharmony_ci } 1178c2ecf20Sopenharmony_ci if (clear) 1188c2ecf20Sopenharmony_ci xas_clear_mark(&xas, MEMFD_TAG_PINNED); 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci latency += cache_count; 1218c2ecf20Sopenharmony_ci if (latency < XA_CHECK_SCHED) 1228c2ecf20Sopenharmony_ci continue; 1238c2ecf20Sopenharmony_ci latency = 0; 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci xas_pause(&xas); 1268c2ecf20Sopenharmony_ci xas_unlock_irq(&xas); 1278c2ecf20Sopenharmony_ci cond_resched(); 1288c2ecf20Sopenharmony_ci xas_lock_irq(&xas); 1298c2ecf20Sopenharmony_ci } 1308c2ecf20Sopenharmony_ci xas_unlock_irq(&xas); 1318c2ecf20Sopenharmony_ci } 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci return error; 1348c2ecf20Sopenharmony_ci} 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_cistatic unsigned int *memfd_file_seals_ptr(struct file *file) 1378c2ecf20Sopenharmony_ci{ 1388c2ecf20Sopenharmony_ci if (shmem_file(file)) 1398c2ecf20Sopenharmony_ci return &SHMEM_I(file_inode(file))->seals; 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci#ifdef CONFIG_HUGETLBFS 1428c2ecf20Sopenharmony_ci if (is_file_hugepages(file)) 1438c2ecf20Sopenharmony_ci return &HUGETLBFS_I(file_inode(file))->seals; 1448c2ecf20Sopenharmony_ci#endif 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci return NULL; 1478c2ecf20Sopenharmony_ci} 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci#define F_ALL_SEALS (F_SEAL_SEAL | \ 1508c2ecf20Sopenharmony_ci F_SEAL_SHRINK | \ 1518c2ecf20Sopenharmony_ci F_SEAL_GROW | \ 1528c2ecf20Sopenharmony_ci F_SEAL_WRITE | \ 1538c2ecf20Sopenharmony_ci F_SEAL_FUTURE_WRITE) 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_cistatic int memfd_add_seals(struct file *file, unsigned int seals) 1568c2ecf20Sopenharmony_ci{ 1578c2ecf20Sopenharmony_ci struct inode *inode = file_inode(file); 1588c2ecf20Sopenharmony_ci unsigned int *file_seals; 1598c2ecf20Sopenharmony_ci int error; 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci /* 1628c2ecf20Sopenharmony_ci * SEALING 1638c2ecf20Sopenharmony_ci * Sealing allows multiple parties to share a tmpfs or hugetlbfs file 1648c2ecf20Sopenharmony_ci * but restrict access to a specific subset of file operations. Seals 1658c2ecf20Sopenharmony_ci * can only be added, but never removed. This way, mutually untrusted 1668c2ecf20Sopenharmony_ci * parties can share common memory regions with a well-defined policy. 1678c2ecf20Sopenharmony_ci * A malicious peer can thus never perform unwanted operations on a 1688c2ecf20Sopenharmony_ci * shared object. 1698c2ecf20Sopenharmony_ci * 1708c2ecf20Sopenharmony_ci * Seals are only supported on special tmpfs or hugetlbfs files and 1718c2ecf20Sopenharmony_ci * always affect the whole underlying inode. Once a seal is set, it 1728c2ecf20Sopenharmony_ci * may prevent some kinds of access to the file. Currently, the 1738c2ecf20Sopenharmony_ci * following seals are defined: 1748c2ecf20Sopenharmony_ci * SEAL_SEAL: Prevent further seals from being set on this file 1758c2ecf20Sopenharmony_ci * SEAL_SHRINK: Prevent the file from shrinking 1768c2ecf20Sopenharmony_ci * SEAL_GROW: Prevent the file from growing 1778c2ecf20Sopenharmony_ci * SEAL_WRITE: Prevent write access to the file 1788c2ecf20Sopenharmony_ci * 1798c2ecf20Sopenharmony_ci * As we don't require any trust relationship between two parties, we 1808c2ecf20Sopenharmony_ci * must prevent seals from being removed. Therefore, sealing a file 1818c2ecf20Sopenharmony_ci * only adds a given set of seals to the file, it never touches 1828c2ecf20Sopenharmony_ci * existing seals. Furthermore, the "setting seals"-operation can be 1838c2ecf20Sopenharmony_ci * sealed itself, which basically prevents any further seal from being 1848c2ecf20Sopenharmony_ci * added. 1858c2ecf20Sopenharmony_ci * 1868c2ecf20Sopenharmony_ci * Semantics of sealing are only defined on volatile files. Only 1878c2ecf20Sopenharmony_ci * anonymous tmpfs and hugetlbfs files support sealing. More 1888c2ecf20Sopenharmony_ci * importantly, seals are never written to disk. Therefore, there's 1898c2ecf20Sopenharmony_ci * no plan to support it on other file types. 1908c2ecf20Sopenharmony_ci */ 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci if (!(file->f_mode & FMODE_WRITE)) 1938c2ecf20Sopenharmony_ci return -EPERM; 1948c2ecf20Sopenharmony_ci if (seals & ~(unsigned int)F_ALL_SEALS) 1958c2ecf20Sopenharmony_ci return -EINVAL; 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci inode_lock(inode); 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_ci file_seals = memfd_file_seals_ptr(file); 2008c2ecf20Sopenharmony_ci if (!file_seals) { 2018c2ecf20Sopenharmony_ci error = -EINVAL; 2028c2ecf20Sopenharmony_ci goto unlock; 2038c2ecf20Sopenharmony_ci } 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_ci if (*file_seals & F_SEAL_SEAL) { 2068c2ecf20Sopenharmony_ci error = -EPERM; 2078c2ecf20Sopenharmony_ci goto unlock; 2088c2ecf20Sopenharmony_ci } 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci if ((seals & F_SEAL_WRITE) && !(*file_seals & F_SEAL_WRITE)) { 2118c2ecf20Sopenharmony_ci error = mapping_deny_writable(file->f_mapping); 2128c2ecf20Sopenharmony_ci if (error) 2138c2ecf20Sopenharmony_ci goto unlock; 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci error = memfd_wait_for_pins(file->f_mapping); 2168c2ecf20Sopenharmony_ci if (error) { 2178c2ecf20Sopenharmony_ci mapping_allow_writable(file->f_mapping); 2188c2ecf20Sopenharmony_ci goto unlock; 2198c2ecf20Sopenharmony_ci } 2208c2ecf20Sopenharmony_ci } 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci *file_seals |= seals; 2238c2ecf20Sopenharmony_ci error = 0; 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ciunlock: 2268c2ecf20Sopenharmony_ci inode_unlock(inode); 2278c2ecf20Sopenharmony_ci return error; 2288c2ecf20Sopenharmony_ci} 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_cistatic int memfd_get_seals(struct file *file) 2318c2ecf20Sopenharmony_ci{ 2328c2ecf20Sopenharmony_ci unsigned int *seals = memfd_file_seals_ptr(file); 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ci return seals ? *seals : -EINVAL; 2358c2ecf20Sopenharmony_ci} 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_cilong memfd_fcntl(struct file *file, unsigned int cmd, unsigned long arg) 2388c2ecf20Sopenharmony_ci{ 2398c2ecf20Sopenharmony_ci long error; 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci switch (cmd) { 2428c2ecf20Sopenharmony_ci case F_ADD_SEALS: 2438c2ecf20Sopenharmony_ci /* disallow upper 32bit */ 2448c2ecf20Sopenharmony_ci if (arg > UINT_MAX) 2458c2ecf20Sopenharmony_ci return -EINVAL; 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_ci error = memfd_add_seals(file, arg); 2488c2ecf20Sopenharmony_ci break; 2498c2ecf20Sopenharmony_ci case F_GET_SEALS: 2508c2ecf20Sopenharmony_ci error = memfd_get_seals(file); 2518c2ecf20Sopenharmony_ci break; 2528c2ecf20Sopenharmony_ci default: 2538c2ecf20Sopenharmony_ci error = -EINVAL; 2548c2ecf20Sopenharmony_ci break; 2558c2ecf20Sopenharmony_ci } 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_ci return error; 2588c2ecf20Sopenharmony_ci} 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_ci#define MFD_NAME_PREFIX "memfd:" 2618c2ecf20Sopenharmony_ci#define MFD_NAME_PREFIX_LEN (sizeof(MFD_NAME_PREFIX) - 1) 2628c2ecf20Sopenharmony_ci#define MFD_NAME_MAX_LEN (NAME_MAX - MFD_NAME_PREFIX_LEN) 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_ci#define MFD_ALL_FLAGS (MFD_CLOEXEC | MFD_ALLOW_SEALING | MFD_HUGETLB) 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_ciSYSCALL_DEFINE2(memfd_create, 2678c2ecf20Sopenharmony_ci const char __user *, uname, 2688c2ecf20Sopenharmony_ci unsigned int, flags) 2698c2ecf20Sopenharmony_ci{ 2708c2ecf20Sopenharmony_ci unsigned int *file_seals; 2718c2ecf20Sopenharmony_ci struct file *file; 2728c2ecf20Sopenharmony_ci int fd, error; 2738c2ecf20Sopenharmony_ci char *name; 2748c2ecf20Sopenharmony_ci long len; 2758c2ecf20Sopenharmony_ci 2768c2ecf20Sopenharmony_ci if (!(flags & MFD_HUGETLB)) { 2778c2ecf20Sopenharmony_ci if (flags & ~(unsigned int)MFD_ALL_FLAGS) 2788c2ecf20Sopenharmony_ci return -EINVAL; 2798c2ecf20Sopenharmony_ci } else { 2808c2ecf20Sopenharmony_ci /* Allow huge page size encoding in flags. */ 2818c2ecf20Sopenharmony_ci if (flags & ~(unsigned int)(MFD_ALL_FLAGS | 2828c2ecf20Sopenharmony_ci (MFD_HUGE_MASK << MFD_HUGE_SHIFT))) 2838c2ecf20Sopenharmony_ci return -EINVAL; 2848c2ecf20Sopenharmony_ci } 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_ci /* length includes terminating zero */ 2878c2ecf20Sopenharmony_ci len = strnlen_user(uname, MFD_NAME_MAX_LEN + 1); 2888c2ecf20Sopenharmony_ci if (len <= 0) 2898c2ecf20Sopenharmony_ci return -EFAULT; 2908c2ecf20Sopenharmony_ci if (len > MFD_NAME_MAX_LEN + 1) 2918c2ecf20Sopenharmony_ci return -EINVAL; 2928c2ecf20Sopenharmony_ci 2938c2ecf20Sopenharmony_ci name = kmalloc(len + MFD_NAME_PREFIX_LEN, GFP_KERNEL); 2948c2ecf20Sopenharmony_ci if (!name) 2958c2ecf20Sopenharmony_ci return -ENOMEM; 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_ci strcpy(name, MFD_NAME_PREFIX); 2988c2ecf20Sopenharmony_ci if (copy_from_user(&name[MFD_NAME_PREFIX_LEN], uname, len)) { 2998c2ecf20Sopenharmony_ci error = -EFAULT; 3008c2ecf20Sopenharmony_ci goto err_name; 3018c2ecf20Sopenharmony_ci } 3028c2ecf20Sopenharmony_ci 3038c2ecf20Sopenharmony_ci /* terminating-zero may have changed after strnlen_user() returned */ 3048c2ecf20Sopenharmony_ci if (name[len + MFD_NAME_PREFIX_LEN - 1]) { 3058c2ecf20Sopenharmony_ci error = -EFAULT; 3068c2ecf20Sopenharmony_ci goto err_name; 3078c2ecf20Sopenharmony_ci } 3088c2ecf20Sopenharmony_ci 3098c2ecf20Sopenharmony_ci fd = get_unused_fd_flags((flags & MFD_CLOEXEC) ? O_CLOEXEC : 0); 3108c2ecf20Sopenharmony_ci if (fd < 0) { 3118c2ecf20Sopenharmony_ci error = fd; 3128c2ecf20Sopenharmony_ci goto err_name; 3138c2ecf20Sopenharmony_ci } 3148c2ecf20Sopenharmony_ci 3158c2ecf20Sopenharmony_ci if (flags & MFD_HUGETLB) { 3168c2ecf20Sopenharmony_ci struct user_struct *user = NULL; 3178c2ecf20Sopenharmony_ci 3188c2ecf20Sopenharmony_ci file = hugetlb_file_setup(name, 0, VM_NORESERVE, &user, 3198c2ecf20Sopenharmony_ci HUGETLB_ANONHUGE_INODE, 3208c2ecf20Sopenharmony_ci (flags >> MFD_HUGE_SHIFT) & 3218c2ecf20Sopenharmony_ci MFD_HUGE_MASK); 3228c2ecf20Sopenharmony_ci } else 3238c2ecf20Sopenharmony_ci file = shmem_file_setup(name, 0, VM_NORESERVE); 3248c2ecf20Sopenharmony_ci if (IS_ERR(file)) { 3258c2ecf20Sopenharmony_ci error = PTR_ERR(file); 3268c2ecf20Sopenharmony_ci goto err_fd; 3278c2ecf20Sopenharmony_ci } 3288c2ecf20Sopenharmony_ci file->f_mode |= FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE; 3298c2ecf20Sopenharmony_ci file->f_flags |= O_LARGEFILE; 3308c2ecf20Sopenharmony_ci 3318c2ecf20Sopenharmony_ci if (flags & MFD_ALLOW_SEALING) { 3328c2ecf20Sopenharmony_ci file_seals = memfd_file_seals_ptr(file); 3338c2ecf20Sopenharmony_ci if (file_seals) 3348c2ecf20Sopenharmony_ci *file_seals &= ~F_SEAL_SEAL; 3358c2ecf20Sopenharmony_ci } 3368c2ecf20Sopenharmony_ci 3378c2ecf20Sopenharmony_ci fd_install(fd, file); 3388c2ecf20Sopenharmony_ci kfree(name); 3398c2ecf20Sopenharmony_ci return fd; 3408c2ecf20Sopenharmony_ci 3418c2ecf20Sopenharmony_cierr_fd: 3428c2ecf20Sopenharmony_ci put_unused_fd(fd); 3438c2ecf20Sopenharmony_cierr_name: 3448c2ecf20Sopenharmony_ci kfree(name); 3458c2ecf20Sopenharmony_ci return error; 3468c2ecf20Sopenharmony_ci} 347