Skip to content
File

Blob: src/pyodide/internal/readOnlyFS.ts

typescript104 lines
1// Copyright (c) 2026 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4 
5type ReadFn<Info> = FSStreamOps<Info>['read'];
6 
7// When we load shared libraries we need to ensure they come from a read only file system.
8 
9// Map to store the original trusted read function for each read-only filesystem. We store the
10// function itself to prevent attacks where user code modifies stream_ops.read after filesystem
11// creation and tricks us into loading a dynamically generated so file.
12const TRUSTED_READ_FUNCS: Map<object, ReadFn<any>> = new Map();
13 
14export function getTrustedReadFunc<Info>(
15 node: FSNode<Info>
16): ReadFn<Info> | undefined {
17 return TRUSTED_READ_FUNCS.get(node.mount.type);
18}
19 
20export function createReadonlyFS<Info>(
21 FSOps: FSOps<Info>,
22 Module: Module
23): EmscriptenFS<Info> {
24 const FS = Module.FS;
25 const ReadOnlyFS: EmscriptenFS<Info> = {
26 mount(mount) {
27 return ReadOnlyFS.createNode(null, '/', mount.opts.info);
28 },
29 createNode(parent, name, info): FSNode<Info> {
30 // eslint-disable-next-line prefer-const
31 let { permissions: mode, isDir } = FSOps.getNodeMode(parent, name, info);
32 if (isDir) {
33 mode |= 1 << 14; // set S_IFDIR
34 } else {
35 mode |= 1 << 15; // set S_IFREG
36 }
37 const node = FS.createNode(parent, name, mode);
38 node.node_ops = ReadOnlyFS.node_ops;
39 node.stream_ops = ReadOnlyFS.stream_ops;
40 FSOps.setNodeAttributes(node, info, isDir);
41 return node;
42 },
43 node_ops: {
44 getattr(node) {
45 const size = node.usedBytes;
46 const mode = node.mode;
47 const t = new Date(node.modtime);
48 const blksize = 4096;
49 const blocks = ((size + blksize - 1) / blksize) | 0;
50 return {
51 dev: 1,
52 ino: node.id,
53 mode,
54 nlink: 1,
55 uid: 0,
56 gid: 0,
57 rdev: 0,
58 size,
59 atime: t,
60 mtime: t,
61 ctime: t,
62 blksize,
63 blocks,
64 };
65 },
66 readdir(node) {
67 return FSOps.readdir(node);
68 },
69 lookup(parent, name) {
70 const child = FSOps.lookup(parent, name);
71 if (child === undefined) {
72 throw FS.genericErrors?.[44] ?? new FS.ErrnoError(44); // ENOENT
73 }
74 return ReadOnlyFS.createNode(parent, name, child);
75 },
76 },
77 stream_ops: {
78 llseek(stream, offset, whence) {
79 let position = offset;
80 if (whence === 1) {
81 // SEEK_CUR
82 position += stream.position;
83 } else if (whence === 2) {
84 // SEEK_END
85 if (FS.isFile(stream.node.mode)) {
86 position += stream.node.usedBytes;
87 }
88 }
89 return position;
90 },
91 read(stream, buffer, offset, length, position) {
92 if (position >= stream.node.usedBytes) return 0;
93 const size = Math.min(stream.node.usedBytes - position, length);
94 buffer = buffer.subarray(offset, offset + size);
95 return FSOps.read(stream, position, buffer);
96 },
97 },
98 };
99 // Register this filesystem as read-only and store its trusted read function so we can load so
100 // files from it.
101 TRUSTED_READ_FUNCS.set(ReadOnlyFS, ReadOnlyFS.stream_ops.read);
102 return ReadOnlyFS;
103}