1# Tests for recovering from conditions which shouldn't normally
2# happen during normal operation of littlefs
3
4# invalid pointer tests (outside of block_count)
5
6[cases.test_evil_invalid_tail_pointer]
7defines.TAIL_TYPE = ['LFS_TYPE_HARDTAIL', 'LFS_TYPE_SOFTTAIL']
8defines.INVALSET = [0x3, 0x1, 0x2]
9in = "lfs.c"
10code = '''
11    // create littlefs
12    lfs_t lfs;
13    lfs_format(&lfs, cfg) => 0;
14
15    // change tail-pointer to invalid pointers
16    lfs_init(&lfs, cfg) => 0;
17    lfs_mdir_t mdir;
18    lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
19    lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
20            {LFS_MKTAG(LFS_TYPE_HARDTAIL, 0x3ff, 8),
21                (lfs_block_t[2]){
22                    (INVALSET & 0x1) ? 0xcccccccc : 0,
23                    (INVALSET & 0x2) ? 0xcccccccc : 0}})) => 0;
24    lfs_deinit(&lfs) => 0;
25
26    // test that mount fails gracefully
27    lfs_mount(&lfs, cfg) => LFS_ERR_CORRUPT;
28'''
29
30[cases.test_evil_invalid_dir_pointer]
31defines.INVALSET = [0x3, 0x1, 0x2]
32in = "lfs.c"
33code = '''
34    // create littlefs
35    lfs_t lfs;
36    lfs_format(&lfs, cfg) => 0;
37    // make a dir
38    lfs_mount(&lfs, cfg) => 0;
39    lfs_mkdir(&lfs, "dir_here") => 0;
40    lfs_unmount(&lfs) => 0;
41
42    // change the dir pointer to be invalid
43    lfs_init(&lfs, cfg) => 0;
44    lfs_mdir_t mdir;
45    lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
46    // make sure id 1 == our directory
47    uint8_t buffer[1024];
48    lfs_dir_get(&lfs, &mdir,
49            LFS_MKTAG(0x700, 0x3ff, 0),
50            LFS_MKTAG(LFS_TYPE_NAME, 1, strlen("dir_here")), buffer)
51                => LFS_MKTAG(LFS_TYPE_DIR, 1, strlen("dir_here"));
52    assert(memcmp((char*)buffer, "dir_here", strlen("dir_here")) == 0);
53    // change dir pointer
54    lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
55            {LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 1, 8),
56                (lfs_block_t[2]){
57                    (INVALSET & 0x1) ? 0xcccccccc : 0,
58                    (INVALSET & 0x2) ? 0xcccccccc : 0}})) => 0;
59    lfs_deinit(&lfs) => 0;
60
61    // test that accessing our bad dir fails, note there's a number
62    // of ways to access the dir, some can fail, but some don't
63    lfs_mount(&lfs, cfg) => 0;
64    struct lfs_info info;
65    lfs_stat(&lfs, "dir_here", &info) => 0;
66    assert(strcmp(info.name, "dir_here") == 0);
67    assert(info.type == LFS_TYPE_DIR);
68
69    lfs_dir_t dir;
70    lfs_dir_open(&lfs, &dir, "dir_here") => LFS_ERR_CORRUPT;
71    lfs_stat(&lfs, "dir_here/file_here", &info) => LFS_ERR_CORRUPT;
72    lfs_dir_open(&lfs, &dir, "dir_here/dir_here") => LFS_ERR_CORRUPT;
73    lfs_file_t file;
74    lfs_file_open(&lfs, &file, "dir_here/file_here",
75            LFS_O_RDONLY) => LFS_ERR_CORRUPT;
76    lfs_file_open(&lfs, &file, "dir_here/file_here",
77            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_CORRUPT;
78    lfs_unmount(&lfs) => 0;
79'''
80
81[cases.test_evil_invalid_file_pointer]
82in = "lfs.c"
83defines.SIZE = [10, 1000, 100000] # faked file size
84code = '''
85    // create littlefs
86    lfs_t lfs;
87    lfs_format(&lfs, cfg) => 0;
88    // make a file
89    lfs_mount(&lfs, cfg) => 0;
90    lfs_file_t file;
91    lfs_file_open(&lfs, &file, "file_here",
92            LFS_O_WRONLY | LFS_O_CREAT) => 0;
93    lfs_file_close(&lfs, &file) => 0;
94    lfs_unmount(&lfs) => 0;
95
96    // change the file pointer to be invalid
97    lfs_init(&lfs, cfg) => 0;
98    lfs_mdir_t mdir;
99    lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
100    // make sure id 1 == our file
101    uint8_t buffer[1024];
102    lfs_dir_get(&lfs, &mdir,
103            LFS_MKTAG(0x700, 0x3ff, 0),
104            LFS_MKTAG(LFS_TYPE_NAME, 1, strlen("file_here")), buffer)
105                => LFS_MKTAG(LFS_TYPE_REG, 1, strlen("file_here"));
106    assert(memcmp((char*)buffer, "file_here", strlen("file_here")) == 0);
107    // change file pointer
108    lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
109            {LFS_MKTAG(LFS_TYPE_CTZSTRUCT, 1, sizeof(struct lfs_ctz)),
110                &(struct lfs_ctz){0xcccccccc, lfs_tole32(SIZE)}})) => 0;
111    lfs_deinit(&lfs) => 0;
112
113    // test that accessing our bad file fails, note there's a number
114    // of ways to access the dir, some can fail, but some don't
115    lfs_mount(&lfs, cfg) => 0;
116    struct lfs_info info;
117    lfs_stat(&lfs, "file_here", &info) => 0;
118    assert(strcmp(info.name, "file_here") == 0);
119    assert(info.type == LFS_TYPE_REG);
120    assert(info.size == SIZE);
121
122    lfs_file_open(&lfs, &file, "file_here", LFS_O_RDONLY) => 0;
123    lfs_file_read(&lfs, &file, buffer, SIZE) => LFS_ERR_CORRUPT;
124    lfs_file_close(&lfs, &file) => 0;
125
126    // any allocs that traverse CTZ must unfortunately must fail
127    if (SIZE > 2*BLOCK_SIZE) {
128        lfs_mkdir(&lfs, "dir_here") => LFS_ERR_CORRUPT;
129    }
130    lfs_unmount(&lfs) => 0;
131'''
132
133[cases.test_evil_invalid_ctz_pointer] # invalid pointer in CTZ skip-list test
134defines.SIZE = ['2*BLOCK_SIZE', '3*BLOCK_SIZE', '4*BLOCK_SIZE']
135in = "lfs.c"
136code = '''
137    // create littlefs
138    lfs_t lfs;
139    lfs_format(&lfs, cfg) => 0;
140    // make a file
141    lfs_mount(&lfs, cfg) => 0;
142    lfs_file_t file;
143    lfs_file_open(&lfs, &file, "file_here",
144            LFS_O_WRONLY | LFS_O_CREAT) => 0;
145    for (int i = 0; i < SIZE; i++) {
146        char c = 'c';
147        lfs_file_write(&lfs, &file, &c, 1) => 1;
148    }
149    lfs_file_close(&lfs, &file) => 0;
150    lfs_unmount(&lfs) => 0;
151    // change pointer in CTZ skip-list to be invalid
152    lfs_init(&lfs, cfg) => 0;
153    lfs_mdir_t mdir;
154    lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
155    // make sure id 1 == our file and get our CTZ structure
156    uint8_t buffer[4*BLOCK_SIZE];
157    lfs_dir_get(&lfs, &mdir,
158            LFS_MKTAG(0x700, 0x3ff, 0),
159            LFS_MKTAG(LFS_TYPE_NAME, 1, strlen("file_here")), buffer)
160                => LFS_MKTAG(LFS_TYPE_REG, 1, strlen("file_here"));
161    assert(memcmp((char*)buffer, "file_here", strlen("file_here")) == 0);
162    struct lfs_ctz ctz;
163    lfs_dir_get(&lfs, &mdir,
164            LFS_MKTAG(0x700, 0x3ff, 0),
165            LFS_MKTAG(LFS_TYPE_STRUCT, 1, sizeof(struct lfs_ctz)), &ctz)
166                => LFS_MKTAG(LFS_TYPE_CTZSTRUCT, 1, sizeof(struct lfs_ctz));
167    lfs_ctz_fromle32(&ctz);
168    // rewrite block to contain bad pointer
169    uint8_t bbuffer[BLOCK_SIZE];
170    cfg->read(cfg, ctz.head, 0, bbuffer, BLOCK_SIZE) => 0;
171    uint32_t bad = lfs_tole32(0xcccccccc);
172    memcpy(&bbuffer[0], &bad, sizeof(bad));
173    memcpy(&bbuffer[4], &bad, sizeof(bad));
174    cfg->erase(cfg, ctz.head) => 0;
175    cfg->prog(cfg, ctz.head, 0, bbuffer, BLOCK_SIZE) => 0;
176    lfs_deinit(&lfs) => 0;
177
178    // test that accessing our bad file fails, note there's a number
179    // of ways to access the dir, some can fail, but some don't
180    lfs_mount(&lfs, cfg) => 0;
181    struct lfs_info info;
182    lfs_stat(&lfs, "file_here", &info) => 0;
183    assert(strcmp(info.name, "file_here") == 0);
184    assert(info.type == LFS_TYPE_REG);
185    assert(info.size == SIZE);
186
187    lfs_file_open(&lfs, &file, "file_here", LFS_O_RDONLY) => 0;
188    lfs_file_read(&lfs, &file, buffer, SIZE) => LFS_ERR_CORRUPT;
189    lfs_file_close(&lfs, &file) => 0;
190
191    // any allocs that traverse CTZ must unfortunately must fail
192    if (SIZE > 2*BLOCK_SIZE) {
193        lfs_mkdir(&lfs, "dir_here") => LFS_ERR_CORRUPT;
194    }
195    lfs_unmount(&lfs) => 0;
196'''
197
198
199[cases.test_evil_invalid_gstate_pointer]
200defines.INVALSET = [0x3, 0x1, 0x2]
201in = "lfs.c"
202code = '''
203    // create littlefs
204    lfs_t lfs;
205    lfs_format(&lfs, cfg) => 0;
206
207    // create an invalid gstate
208    lfs_init(&lfs, cfg) => 0;
209    lfs_mdir_t mdir;
210    lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
211    lfs_fs_prepmove(&lfs, 1, (lfs_block_t [2]){
212            (INVALSET & 0x1) ? 0xcccccccc : 0,
213            (INVALSET & 0x2) ? 0xcccccccc : 0});
214    lfs_dir_commit(&lfs, &mdir, NULL, 0) => 0;
215    lfs_deinit(&lfs) => 0;
216
217    // test that mount fails gracefully
218    // mount may not fail, but our first alloc should fail when
219    // we try to fix the gstate
220    lfs_mount(&lfs, cfg) => 0;
221    lfs_mkdir(&lfs, "should_fail") => LFS_ERR_CORRUPT;
222    lfs_unmount(&lfs) => 0;
223'''
224
225# cycle detection/recovery tests
226
227[cases.test_evil_mdir_loop] # metadata-pair threaded-list loop test
228in = "lfs.c"
229code = '''
230    // create littlefs
231    lfs_t lfs;
232    lfs_format(&lfs, cfg) => 0;
233
234    // change tail-pointer to point to ourself
235    lfs_init(&lfs, cfg) => 0;
236    lfs_mdir_t mdir;
237    lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
238    lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
239            {LFS_MKTAG(LFS_TYPE_HARDTAIL, 0x3ff, 8),
240                (lfs_block_t[2]){0, 1}})) => 0;
241    lfs_deinit(&lfs) => 0;
242
243    // test that mount fails gracefully
244    lfs_mount(&lfs, cfg) => LFS_ERR_CORRUPT;
245'''
246
247[cases.test_evil_mdir_loop2] # metadata-pair threaded-list 2-length loop test
248in = "lfs.c"
249code = '''
250    // create littlefs with child dir
251    lfs_t lfs;
252    lfs_format(&lfs, cfg) => 0;
253    lfs_mount(&lfs, cfg) => 0;
254    lfs_mkdir(&lfs, "child") => 0;
255    lfs_unmount(&lfs) => 0;
256
257    // find child
258    lfs_init(&lfs, cfg) => 0;
259    lfs_mdir_t mdir;
260    lfs_block_t pair[2];
261    lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
262    lfs_dir_get(&lfs, &mdir,
263            LFS_MKTAG(0x7ff, 0x3ff, 0),
264            LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 1, sizeof(pair)), pair)
265                => LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 1, sizeof(pair));
266    lfs_pair_fromle32(pair);
267    // change tail-pointer to point to root
268    lfs_dir_fetch(&lfs, &mdir, pair) => 0;
269    lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
270            {LFS_MKTAG(LFS_TYPE_HARDTAIL, 0x3ff, 8),
271                (lfs_block_t[2]){0, 1}})) => 0;
272    lfs_deinit(&lfs) => 0;
273
274    // test that mount fails gracefully
275    lfs_mount(&lfs, cfg) => LFS_ERR_CORRUPT;
276'''
277
278[cases.test_evil_mdir_loop_child] # metadata-pair threaded-list 1-length child loop test
279in = "lfs.c"
280code = '''
281    // create littlefs with child dir
282    lfs_t lfs;
283    lfs_format(&lfs, cfg) => 0;
284    lfs_mount(&lfs, cfg) => 0;
285    lfs_mkdir(&lfs, "child") => 0;
286    lfs_unmount(&lfs) => 0;
287
288    // find child
289    lfs_init(&lfs, cfg) => 0;
290    lfs_mdir_t mdir;
291    lfs_block_t pair[2];
292    lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
293    lfs_dir_get(&lfs, &mdir,
294            LFS_MKTAG(0x7ff, 0x3ff, 0),
295            LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 1, sizeof(pair)), pair)
296                => LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 1, sizeof(pair));
297    lfs_pair_fromle32(pair);
298    // change tail-pointer to point to ourself
299    lfs_dir_fetch(&lfs, &mdir, pair) => 0;
300    lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
301            {LFS_MKTAG(LFS_TYPE_HARDTAIL, 0x3ff, 8), pair})) => 0;
302    lfs_deinit(&lfs) => 0;
303
304    // test that mount fails gracefully
305    lfs_mount(&lfs, cfg) => LFS_ERR_CORRUPT;
306'''
307