Skip to content

Commit 5440481

Browse files
authored
Add CramFS implementation (#1)
1 parent 8531e12 commit 5440481

25 files changed

+1713
-1
lines changed

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
tests/_data/** filter=lfs diff=lfs merge=lfs -text

.github/pull_request_template.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<!--
2+
Thank you for submitting a Pull Request. Please:
3+
* Read our commit style guide:
4+
Commit messages should adhere to the following points:
5+
* Separate subject from body with a blank line
6+
* Limit the subject line to 50 characters as much as possible
7+
* Capitalize the subject line
8+
* Do not end the subject line with a period
9+
* Use the imperative mood in the subject line
10+
* The verb should represent what was accomplished (Create, Add, Fix etc)
11+
* Wrap the body at 72 characters
12+
* Use the body to explain the what and why vs. the how
13+
For an example, look at the following link:
14+
https://docs.dissect.tools/en/latest/contributing/style-guide.html#example-commit-message
15+
* Include a description of the proposed changes and how to test them.
16+
* After creation, associate the PR with an issue, under the development section.
17+
Or use closing keywords in the body during creation:
18+
E.G:
19+
* close(|s|d) #<nr>
20+
* fix(|es|ed) #<nr>
21+
* resolve(|s|d) #<nr>
22+
-->

.github/workflows/dissect-ci.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Dissect CI
2+
on:
3+
push:
4+
branches:
5+
- main
6+
tags:
7+
- '*'
8+
pull_request:
9+
workflow_dispatch:
10+
11+
jobs:
12+
ci:
13+
uses: fox-it/dissect-workflow-templates/.github/workflows/dissect-ci-template.yml@main
14+
secrets: inherit
15+
16+
publish:
17+
if: ${{ github.ref_name == 'main' || github.ref_type == 'tag' }}
18+
needs: [ci]
19+
runs-on: ubuntu-latest
20+
environment: dissect_publish
21+
permissions:
22+
id-token: write
23+
steps:
24+
- uses: actions/download-artifact@v4
25+
with:
26+
name: packages
27+
path: dist/
28+
# According to the documentation, it automatically looks inside the `dist/` folder for packages.
29+
- name: Publish package distributions to Pypi
30+
uses: pypa/gh-action-pypi-publish@release/v1
31+
32+
trigger-tests:
33+
needs: [publish]
34+
uses: fox-it/dissect-workflow-templates/.github/workflows/dissect-ci-demand-test-template.yml@main
35+
secrets: inherit
36+
with:
37+
on-demand-test: 'dissect.target'

.gitignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
coverage.xml
2+
.coverage
3+
dist/
4+
.eggs/
5+
*.egg-info/
6+
*.pyc
7+
__pycache__/
8+
.pytest_cache/
9+
tests/_docs/api
10+
tests/_docs/build
11+
.tox/

COPYRIGHT

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Dissect is released as open source by Fox-IT (https://www.fox-it.com) part of NCC Group Plc (https://www.nccgroup.com)
2+
3+
Developed by the Dissect Team (dissect@fox-it.com) and made available at https://github.com/fox-it/dissect.target
4+
5+
License terms: AGPL3 (https://www.gnu.org/licenses/agpl-3.0.html)

LICENSE

Lines changed: 661 additions & 0 deletions
Large diffs are not rendered by default.

MANIFEST.in

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
exclude .gitattributes
2+
exclude .gitignore
3+
recursive-exclude .github/ *
4+
recursive-exclude tests/_data/ *

README.md

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,53 @@
11
# dissect.cramfs
2-
A Dissect module implementing a parser for the Cram file system, commonly used in appliance or device firmware.
2+
3+
A Dissect module implementing a parser for the compressed ROM/RAM file system (or cramfs), commonly used in appliance or device firmware. For more
4+
information, please see [the documentation](https://docs.dissect.tools/en/latest/projects/dissect.cramfs/index.html).
5+
6+
## Requirements
7+
8+
This project is part of the Dissect framework and requires Python.
9+
10+
Information on the supported Python versions can be found in the Getting Started section of [the documentation](https://docs.dissect.tools/en/latest/index.html#getting-started).
11+
12+
## Installation
13+
14+
`dissect.cramfs` is available on [PyPI](https://pypi.org/project/dissect.cramfs/).
15+
16+
```bash
17+
pip install dissect.cramfs
18+
```
19+
20+
## Build and test instructions
21+
22+
This project uses `tox` to build source and wheel distributions. Run the following command from the root folder to build
23+
these:
24+
25+
```bash
26+
tox -e build
27+
```
28+
29+
The build artifacts can be found in the `dist/` directory.
30+
31+
`tox` is also used to run linting and unit tests in a self-contained environment. To run both linting and unit tests
32+
using the default installed Python version, run:
33+
34+
```bash
35+
tox
36+
```
37+
38+
For a more elaborate explanation on how to build and test the project, please see [the
39+
documentation](https://docs.dissect.tools/en/latest/contributing/tooling.html).
40+
41+
## Contributing
42+
43+
The Dissect project encourages any contribution to the codebase. To make your contribution fit into the project, please
44+
refer to [the development guide](https://docs.dissect.tools/en/latest/contributing/developing.html).
45+
46+
## Copyright and license
47+
48+
Dissect is released as open source by Fox-IT (<https://www.fox-it.com>) part of NCC Group Plc
49+
(<https://www.nccgroup.com>).
50+
51+
Developed by the Dissect Team (<dissect@fox-it.com>) and made available at <https://github.com/fox-it/dissect>.
52+
53+
License terms: AGPL3 (<https://www.gnu.org/licenses/agpl-3.0.html>). For more information, see the LICENSE file.

dissect/cramfs/__init__.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from __future__ import annotations
2+
3+
from dissect.cramfs.cramfs import BlockStream, CramFS, INode
4+
from dissect.cramfs.exception import (
5+
Error,
6+
FileNotFoundError,
7+
NotADirectoryError,
8+
NotAFileError,
9+
NotASymlinkError,
10+
)
11+
12+
__all__ = [
13+
"BlockStream",
14+
"CramFS",
15+
"Error",
16+
"FileNotFoundError",
17+
"INode",
18+
"NotADirectoryError",
19+
"NotAFileError",
20+
"NotASymlinkError",
21+
]

dissect/cramfs/c_cramfs.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# References:
2+
# - https://github.com/digiampietro/lzma-uncramfs/blob/master/cramfs_fs.h
3+
# - https://github.com/npitre/cramfs-tools/blob/master/linux/cramfs_fs.h
4+
5+
from __future__ import annotations
6+
7+
from dissect.cstruct import cstruct
8+
9+
cramfs_def = """
10+
#define CRAMFS_MAGIC 0x28cd3d45 /* some random number */
11+
#define CRAMFS_SIGNATURE b"Compressed ROMFS"
12+
#define CRAMFS_BLOCK_SIZE 4096
13+
14+
/*
15+
* Width of various bitfields in struct cramfs_inode.
16+
* Primarily used to generate warnings in mkcramfs.
17+
*/
18+
#define CRAMFS_MODE_WIDTH 16
19+
#define CRAMFS_UID_WIDTH 16
20+
#define CRAMFS_GID_WIDTH 8
21+
#define CRAMFS_NAMELEN_WIDTH 6
22+
#define CRAMFS_OFFSET_WIDTH 26
23+
24+
/*
25+
* Since inode.namelen is a unsigned 6-bit number, the maximum cramfs
26+
* path length is 63 << 2 = 252.
27+
*/
28+
#define CRAMFS_MAXPATHLEN (((1 << CRAMFS_NAMELEN_WIDTH) - 1) << 2)
29+
#define CRAMFS_SIZE_WIDTH 24
30+
31+
struct cramfs_inode {
32+
uint32 mode:16;
33+
uint32 uid:16;
34+
35+
/* SIZE for device files is i_rdev */
36+
uint32 size:24;
37+
uint32 gid:8;
38+
39+
/* NAMELEN is the length of the file name, divided by 4 and rounded up. (cramfs doesn't support hard links.) */
40+
/* OFFSET: For symlinks and non-empty regular files, this
41+
contains the offset (divided by 4) of the file data in
42+
compressed form (starting with an array of block pointers;
43+
see README). For non-empty directories it is the offset
44+
(divided by 4) of the inode of the first file in that
45+
directory. For anything else, offset is zero. */
46+
uint32 namelen:6;
47+
uint32 offset:26;
48+
char name[namelen * 4];
49+
};
50+
51+
struct cramfs_info {
52+
uint32 crc;
53+
uint32 edition;
54+
uint32 blocks;
55+
uint32 files;
56+
};
57+
58+
/*
59+
* Superblock information at the beginning of the FS.
60+
*/
61+
struct cramfs_super_block {
62+
uint32 magic; /* 0x28cd3d45 - random number */
63+
uint32 size; /* length in bytes */
64+
uint32 flags; /* feature flags */
65+
uint32 future; /* reserved for future use */
66+
char signature[16]; /* "Compressed ROMFS" */
67+
cramfs_info fsid; /* unique filesystem info */
68+
char name[16]; /* user-defined name */
69+
cramfs_inode root; /* root inode data */
70+
};
71+
72+
/*
73+
* Feature flags
74+
*
75+
* 0x00000000 - 0x000000ff: features that work for all past kernels
76+
* 0x00000100 - 0xffffffff: features that don't work for past kernels
77+
*/
78+
#define CRAMFS_FLAG_FSID_VERSION_2 0x00000001 /* fsid version #2 */
79+
#define CRAMFS_FLAG_SORTED_DIRS 0x00000002 /* sorted dirs */
80+
#define CRAMFS_FLAG_HOLES 0x00000100 /* support for holes */
81+
#define CRAMFS_FLAG_WRONG_SIGNATURE 0x00000200 /* reserved */
82+
#define CRAMFS_FLAG_SHIFTED_ROOT_OFFSET 0x00000400 /* shifted root fs */
83+
#define CRAMFS_FLAG_BLKSZ_MASK 0x00003800 /* block size mask */
84+
#define CRAMFS_FLAG_COMP_METHOD_MASK 0x0000C000 /* Compression method mask */
85+
#define CRAMFS_FLAG_EXT_BLOCK_POINTERS 0x00000800 /* block pointer extensions */
86+
#define CRAMFS_FLAG_DIRECT_POINTER 0x40000000 /* direct pointers flag */
87+
#define CRAMFS_FLAG_UNCOMPRESSED_BLOCK 0x80000000 /* uncompressed block flag */
88+
89+
#define CRAMFS_FLAG_BLKSZ_SHIFT 11
90+
#define CRAMFS_FLAG_COMP_METHOD_SHIFT 14
91+
#define CRAMFS_FLAG_COMP_METHOD_NONE 0
92+
#define CRAMFS_FLAG_COMP_METHOD_GZIP 1
93+
#define CRAMFS_FLAG_COMP_METHOD_LZMA 2
94+
95+
/*
96+
* Valid values in super.flags. Currently we refuse to mount
97+
* if (flags & ~CRAMFS_SUPPORTED_FLAGS). Maybe that should be
98+
* changed to test super.future instead.
99+
*/
100+
#define CRAMFS_SUPPORTED_FLAGS (0x000000ff | CRAMFS_FLAG_HOLES | CRAMFS_FLAG_WRONG_SIGNATURE | CRAMFS_FLAG_SHIFTED_ROOT_OFFSET | CRAMFS_FLAG_BLKSZ_MASK | CRAMFS_FLAG_COMP_METHOD_MASK)
101+
""" # noqa: E501
102+
103+
c_cramfs = cstruct().load(cramfs_def)

0 commit comments

Comments
 (0)