Skip to content

Commit 634f77e

Browse files
committed
check superblock contents when connecting
1 parent 39702f6 commit 634f77e

File tree

3 files changed

+70
-37
lines changed

3 files changed

+70
-37
lines changed

lib/superblock.ml

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
let magic = "littlefs"
1+
let magic = Cstruct.of_string "littlefs"
22
(* for whatever bonkers reason, the major/minor versions
33
* are big-endian, not little-endian like everything else,
44
* even though they're not part of a tag and the functions writing
@@ -31,7 +31,8 @@ type superblock = {
3131
} [@@little_endian]]
3232

3333
let parse cs =
34-
{
34+
if Cstruct.length cs < sizeof_superblock then Error (`Msg "superblock inline struct too small")
35+
else Ok {
3536
version_minor = get_superblock_version_minor cs;
3637
version_major = get_superblock_version_major cs;
3738
block_size = get_superblock_block_size cs;
@@ -62,7 +63,21 @@ let name =
6263
id = 0;
6364
length = 8; })
6465
in
65-
(tag, Cstruct.of_string magic)
66+
(tag, magic)
67+
68+
let is_valid_name (tag, data) =
69+
tag.Tag.length = 8 &&
70+
tag.Tag.valid = true &&
71+
tag.Tag.type3 = (Tag.LFS_TYPE_NAME, 0xff) &&
72+
tag.Tag.id = 0 &&
73+
Cstruct.equal magic data
74+
75+
let is_valid_superblock (tag, data) =
76+
tag.Tag.valid = true &&
77+
tag.Tag.type3 = (Tag.LFS_TYPE_STRUCT, 0x01) &&
78+
tag.Tag.id = 0 &&
79+
tag.Tag.length = sizeof_superblock &&
80+
Cstruct.length data = sizeof_superblock
6681

6782
let inline_struct block_size block_count =
6883
let entry = {

lib_test/test_chamelon.ml

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -69,21 +69,6 @@ module Tag = struct
6969

7070
end
7171

72-
module Superblock = struct
73-
let test_zero () =
74-
let cs = Cstruct.(create @@ Chamelon.Superblock.sizeof_superblock) in
75-
let sb = Chamelon.Superblock.parse cs in
76-
Alcotest.(check int) "major version" 0 sb.version_major;
77-
Alcotest.(check int) "minor version" 0 sb.version_minor;
78-
Alcotest.(check int32) "block size" Int32.zero sb.block_size;
79-
Alcotest.(check int32) "block count" Int32.zero sb.block_count;
80-
Alcotest.(check int32) "name length maximum" Int32.zero sb.name_length_max;
81-
Alcotest.(check int32) "file size maximum" Int32.zero sb.file_size_max;
82-
Alcotest.(check int32) "file attributes size maximum" Int32.zero sb.file_attribute_size_max
83-
84-
85-
end
86-
8772
module Block = struct
8873
module Block = Chamelon.Block
8974

@@ -276,9 +261,6 @@ let () =
276261
tc "write: all bits are 1" `Quick Tag.write_maxint;
277262
tc "roundtrip print/parse for a data-bearing tag" `Quick Tag.roundtrip;
278263
]);
279-
( "superblock", [
280-
tc "all bits are zero" `Quick Superblock.test_zero;
281-
]);
282264
( "block", [
283265
tc "write a superblock commit to a block" `Quick Block.commit_superblock;
284266
tc "writing a block with different revision count gives different CRC" `Quick Block.revision_count_matters;

mirage/fs.ml

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,38 @@ module Make(Sectors: Mirage_block.S)(Clock : Mirage_clock.PCLOCK) = struct
720720

721721
end
722722

723+
(* [block_size_device] is the block size used by the underlying block device which
724+
* we're trying to mount a filesystem from,
725+
* as opposed to the block size recorded in the filesystem's superblock *)
726+
let check_superblock ~program_block_size ~block_size_device cs =
727+
match Chamelon.Block.of_cstruct ~program_block_size cs with
728+
| Error (`Msg s) ->
729+
Log.err (fun f -> f "error parsing block when checking superblock: %s" s);
730+
Lwt.return @@ Error (`Not_found Mirage_kv.Key.empty)
731+
| Ok parsed_block ->
732+
(* does this block have the expected superblock entries? *)
733+
(* the order of entries is strictly specified: name, then the inline struct, then
734+
* any other entries in the superblock *)
735+
match Chamelon.Block.entries parsed_block with
736+
| maybe_name :: maybe_struct :: _ when
737+
Chamelon.Superblock.is_valid_name maybe_name &&
738+
Chamelon.Superblock.is_valid_superblock maybe_struct -> begin
739+
match Chamelon.Superblock.parse (snd maybe_struct) with
740+
| Error (`Msg s) ->
741+
Log.err (fun f -> f "error parsing block when checking superblock: %s" s);
742+
Lwt.return @@ Error (`Not_found Mirage_kv.Key.empty)
743+
| Ok sb ->
744+
if sb.version_major != fst Chamelon.Superblock.version then begin
745+
Log.err (fun f -> f "filesystem is an incompatible version");
746+
Lwt.return @@ Error (`Not_found Mirage_kv.Key.empty)
747+
end else if not @@ Int32.equal sb.block_size block_size_device then begin
748+
Log.err (fun f -> f "filesystem expects a block device with size %ld but the device block size is %ld" sb.block_size block_size_device);
749+
Lwt.return @@ Error (`Not_found Mirage_kv.Key.empty)
750+
end else Lwt.return @@ Ok ()
751+
end
752+
| _ -> Log.err (fun f -> f "expected entries not found on parsed superblock");
753+
Lwt.return @@ Error (`Not_found Mirage_kv.Key.empty)
754+
723755
(* `device` should be an already-connected block device *)
724756
let connect ~program_block_size ~block_size device : (t, error) result Lwt.t =
725757
This_Block.connect ~block_size device >>= fun block ->
@@ -728,23 +760,27 @@ module Make(Sectors: Mirage_block.S)(Clock : Mirage_clock.PCLOCK) = struct
728760
This_Block.read block 0L [first_block] >>= function
729761
| Error e ->
730762
Log.err (fun f -> f "first block read failed: %a" This_Block.pp_error e);
731-
Lwt.return @@ Error (`Not_found (Mirage_kv.Key.empty))
763+
Lwt.return @@ Error (`Not_found Mirage_kv.Key.empty)
732764
| Ok () ->
733-
let lookahead = ref {offset = 0; blocks = []} in
734-
let t = {block; block_size; program_block_size; lookahead; new_block_mutex = Lwt_mutex.create ()} in
735-
Lwt_mutex.lock t.new_block_mutex >>= fun () ->
736-
Traverse.follow_links t [] (Chamelon.Entry.Metadata root_pair) >>= function
737-
| Error _e ->
738-
Lwt_mutex.unlock t.new_block_mutex;
739-
Log.err (fun f -> f "couldn't get list of used blocks");
740-
Lwt.return @@ Error (`Not_found Mirage_kv.Key.empty)
741-
| Ok used_blocks ->
742-
Log.debug (fun f -> f "found %d used blocks on block-based key-value store: %a" (List.length used_blocks) Fmt.(list ~sep:sp int64) used_blocks);
743-
let open Allocate in
744-
let offset, blocks = unused t used_blocks in
745-
let lookahead = ref {blocks; offset} in
746-
Lwt_mutex.unlock t.new_block_mutex;
747-
Lwt.return @@ Ok {t with lookahead; block; block_size; program_block_size}
765+
(* make sure the block is parseable and block size matches *)
766+
check_superblock ~program_block_size ~block_size_device:(Int32.of_int block_size) first_block >>= function
767+
| Error _ as e -> Lwt.return e
768+
| Ok () ->
769+
let lookahead = ref {offset = 0; blocks = []} in
770+
let t = {block; block_size; program_block_size; lookahead; new_block_mutex = Lwt_mutex.create ()} in
771+
Lwt_mutex.lock t.new_block_mutex >>= fun () ->
772+
Traverse.follow_links t [] (Chamelon.Entry.Metadata root_pair) >>= function
773+
| Error _e ->
774+
Lwt_mutex.unlock t.new_block_mutex;
775+
Log.err (fun f -> f "couldn't get list of used blocks");
776+
Lwt.return @@ Error (`Not_found Mirage_kv.Key.empty)
777+
| Ok used_blocks ->
778+
Log.debug (fun f -> f "found %d used blocks on block-based key-value store: %a" (List.length used_blocks) Fmt.(list ~sep:sp int64) used_blocks);
779+
let open Allocate in
780+
let offset, blocks = unused t used_blocks in
781+
let lookahead = ref {blocks; offset} in
782+
Lwt_mutex.unlock t.new_block_mutex;
783+
Lwt.return @@ Ok {t with lookahead; block; block_size; program_block_size}
748784

749785
let format ~program_block_size ~block_size (sectors : Sectors.t) :
750786
(unit, write_error) result Lwt.t =

0 commit comments

Comments
 (0)