commit jefferson for openSUSE:Factory
Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package jefferson for openSUSE:Factory checked in at 2022-10-25 11:20:06 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/jefferson (Old) and /work/SRC/openSUSE:Factory/.jefferson.new.2275 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Package is "jefferson" Tue Oct 25 11:20:06 2022 rev:2 rq:1030940 version:0.4.1+git.20220705 Changes: -------- --- /work/SRC/openSUSE:Factory/jefferson/jefferson.changes 2020-08-19 18:52:12.479700002 +0200 +++ /work/SRC/openSUSE:Factory/.jefferson.new.2275/jefferson.changes 2022-10-25 11:20:32.838208442 +0200 @@ -1,0 +2,42 @@ +Fri Oct 14 13:16:31 UTC 2022 - mardnh@gmx.de + +- Update to version 0.4.1+git.20220705: + * Fix is_safe_path call to use absolute path rather than + relative path to execution directory. + * Fix extraction of files with size greater than one erase block. + * Remove unnecessary log call. + * Keep xattr, xref, and summary nodetypes in order to propely + identify unknown node types. + * Remove handling of xref nodes, xattr nodes, summary nodes. + * Use inode indexed dicts for inodes, dirent, and xref entries. + * Simplify filesystem structure. + * Fix duplicate inodes handling. + * Fix support for python 3.10 by pinning python-lzo to 1.14. + * Better handling of decompression error + simpler endianness + logging. + * Memory-mapped file support + * Add support for LZO compression. + * Revert the symlink path traversal check as it does not present + a direct risk to normal end users. Those checks can be + implemented by other tools where required. + * Fix path traversal security vulnerability by canonicalizing + path names of every inodes and discarding inodes with a path + pointing outside of the extraction directory. + * Autodetect endianness rather than scan the filesystem twice, + one for each possible endianness. We make the assumption that + a JFFS2 has always a fixed endianness and that nodes won't + switch between endianness in the middle of a filesystem. + * Add support for JFFS2 old magic signature (0x1984). + * pin cstruct version to 2.1 so we don't end up with breaking + API changes in the future. Moving to more recent versions + should be done manually once it's been tested that jefferson + still works with the newly released version of cstruct. + * Fix set_endianness to support cstruct version 2.1. + * Converted python2 encode("hex") to python3 hex() + * Switched to lzma in python stdlib + * Convert to python3. +- Drop patches: + * 18.patch + * jefferson-use-pylzma.patch + +------------------------------------------------------------------- Old: ---- 18.patch jefferson-0.3+git.20160616.tar.xz jefferson-use-pylzma.patch New: ---- jefferson-0.4.1+git.20220705.tar.xz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ jefferson.spec ++++++ --- /var/tmp/diff_new_pack.adn8Tc/_old 2022-10-25 11:20:33.794210562 +0200 +++ /var/tmp/diff_new_pack.adn8Tc/_new 2022-10-25 11:20:33.798210571 +0200 @@ -1,7 +1,8 @@ # # spec file for package jefferson # -# Copyright (c) 2020, Martin Hauke <mardnh@gmx.de> +# Copyright (c) 2022 SUSE LLC +# Copyright (c) 2020-2022, Martin Hauke <mardnh@gmx.de> # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -17,23 +18,20 @@ Name: jefferson -Version: 0.3+git.20160616 +Version: 0.4.1+git.20220705 Release: 0 Summary: JFFS2 filesystem extraction tool License: MIT Group: Development/Tools/Other URL: https://github.com/sviehb/jefferson Source: %{name}-%{version}.tar.xz -# Add support for python3 -Patch0: https://github.com/sviehb/jefferson/pull/18.patch -Patch1: jefferson-use-pylzma.patch BuildRequires: fdupes BuildRequires: help2man BuildRequires: python-rpm-macros -BuildRequires: python3-cstruct >= 1.5 -BuildRequires: python3-setuptools +BuildRequires: python3-cstruct >= 2.1 BuildRequires: python3-pylzma -Requires: python3-cstruct >= 1.5 +BuildRequires: python3-setuptools +Requires: python3-cstruct >= 2.1 Requires: python3-pylzma BuildArch: noarch @@ -52,8 +50,6 @@ %prep %setup -q -%patch0 -p1 -%patch1 -p1 chmod -x README.md %build ++++++ _service ++++++ --- /var/tmp/diff_new_pack.adn8Tc/_old 2022-10-25 11:20:33.858210704 +0200 +++ /var/tmp/diff_new_pack.adn8Tc/_new 2022-10-25 11:20:33.862210712 +0200 @@ -5,7 +5,7 @@ <param name="revision">master</param> <param name="scm">git</param> <param name="changesgenerate">enable</param> - <param name="versionformat">0.3+git.%cd</param> + <param name="versionformat">0.4.1+git.%cd</param> </service> <service mode="disabled" name="recompress"> <param name="file">*.tar</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.adn8Tc/_old 2022-10-25 11:20:33.882210757 +0200 +++ /var/tmp/diff_new_pack.adn8Tc/_new 2022-10-25 11:20:33.886210766 +0200 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/sviehb/jefferson.git</param> - <param name="changesrevision">6f9169bad3ceb4e212fae62ad710eeca3350226b</param></service></servicedata> + <param name="changesrevision">ecc51d7ab500e6286c80154f89388e3173784081</param></service></servicedata> (No newline at EOF) ++++++ jefferson-0.3+git.20160616.tar.xz -> jefferson-0.4.1+git.20220705.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jefferson-0.3+git.20160616/.git/FETCH_HEAD new/jefferson-0.4.1+git.20220705/.git/FETCH_HEAD --- old/jefferson-0.3+git.20160616/.git/FETCH_HEAD 1970-01-01 01:00:00.000000000 +0100 +++ new/jefferson-0.4.1+git.20220705/.git/FETCH_HEAD 2022-07-05 10:56:07.000000000 +0200 @@ -0,0 +1,4 @@ +ecc51d7ab500e6286c80154f89388e3173784081 branch 'master' of https://github.com/sviehb/jefferson +5d8e2f0b2f4e6d0999382614f37981d6ca6c3d9f not-for-merge branch 'fix-inode-versioning' of https://github.com/sviehb/jefferson +684069ea678c388d7fe58a61e671e4fa5a2a3c3f not-for-merge branch 'fix-lzo-python310' of https://github.com/sviehb/jefferson +da60d313db2be447b6f8c4ff2cdcca40292faf6d not-for-merge branch 'fix-path-traversal' of https://github.com/sviehb/jefferson diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jefferson-0.3+git.20160616/.git/ORIG_HEAD new/jefferson-0.4.1+git.20220705/.git/ORIG_HEAD --- old/jefferson-0.3+git.20160616/.git/ORIG_HEAD 2016-06-16 16:41:06.000000000 +0200 +++ new/jefferson-0.4.1+git.20220705/.git/ORIG_HEAD 2022-07-05 10:56:07.000000000 +0200 @@ -1 +1 @@ -6f9169bad3ceb4e212fae62ad710eeca3350226b +ecc51d7ab500e6286c80154f89388e3173784081 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jefferson-0.3+git.20160616/.git/config new/jefferson-0.4.1+git.20220705/.git/config --- old/jefferson-0.3+git.20160616/.git/config 2016-06-16 16:41:06.000000000 +0200 +++ new/jefferson-0.4.1+git.20220705/.git/config 2022-07-05 10:56:07.000000000 +0200 @@ -1,11 +1,15 @@ [core] - repositoryformatversion = 0 + repositoryformatversion = 1 filemode = true bare = false logallrefupdates = true [remote "origin"] url = https://github.com/sviehb/jefferson.git fetch = +refs/heads/*:refs/remotes/origin/* + promisor = true + partialclonefilter = tree:0 [branch "master"] remote = origin merge = refs/heads/master +[extensions] + partialClone = origin diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jefferson-0.3+git.20160616/.git/hooks/fsmonitor-watchman.sample new/jefferson-0.4.1+git.20220705/.git/hooks/fsmonitor-watchman.sample --- old/jefferson-0.3+git.20160616/.git/hooks/fsmonitor-watchman.sample 2016-06-16 16:41:06.000000000 +0200 +++ new/jefferson-0.4.1+git.20220705/.git/hooks/fsmonitor-watchman.sample 2022-07-05 10:56:07.000000000 +0200 @@ -86,12 +86,13 @@ # recency index to select candidate nodes and "fields" to limit the # output to file names only. Then we're using the "expression" term to # further constrain the results. + my $last_update_line = ""; if (substr($last_update_token, 0, 1) eq "c") { $last_update_token = "\"$last_update_token\""; + $last_update_line = qq[\n"since": $last_update_token,]; } my $query = <<" END"; - ["query", "$git_work_tree", { - "since": $last_update_token, + ["query", "$git_work_tree", {$last_update_line "fields": ["name"], "expression": ["not", ["dirname", ".git"]] }] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jefferson-0.3+git.20160616/.git/hooks/pre-push.sample new/jefferson-0.4.1+git.20220705/.git/hooks/pre-push.sample --- old/jefferson-0.3+git.20160616/.git/hooks/pre-push.sample 2016-06-16 16:41:06.000000000 +0200 +++ new/jefferson-0.4.1+git.20220705/.git/hooks/pre-push.sample 2022-07-05 10:56:07.000000000 +0200 @@ -14,7 +14,7 @@ # Information about the commits which are being pushed is supplied as lines to # the standard input in the form: # -# <local ref> <local sha1> <remote ref> <remote sha1> +# <local ref> <local oid> <remote ref> <remote oid> # # This sample shows how to prevent push of commits where the log message starts # with "WIP" (work in progress). @@ -22,27 +22,27 @@ remote="$1" url="$2" -z40=0000000000000000000000000000000000000000 +zero=$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0') -while read local_ref local_sha remote_ref remote_sha +while read local_ref local_oid remote_ref remote_oid do - if [ "$local_sha" = $z40 ] + if test "$local_oid" = "$zero" then # Handle delete : else - if [ "$remote_sha" = $z40 ] + if test "$remote_oid" = "$zero" then # New branch, examine all commits - range="$local_sha" + range="$local_oid" else # Update to existing branch, examine new commits - range="$remote_sha..$local_sha" + range="$remote_oid..$local_oid" fi # Check for WIP commit - commit=`git rev-list -n 1 --grep '^WIP' "$range"` - if [ -n "$commit" ] + commit=$(git rev-list -n 1 --grep '^WIP' "$range") + if test -n "$commit" then echo >&2 "Found WIP commit in $local_ref, not pushing" exit 1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jefferson-0.3+git.20160616/.git/hooks/push-to-checkout.sample new/jefferson-0.4.1+git.20220705/.git/hooks/push-to-checkout.sample --- old/jefferson-0.3+git.20160616/.git/hooks/push-to-checkout.sample 1970-01-01 01:00:00.000000000 +0100 +++ new/jefferson-0.4.1+git.20220705/.git/hooks/push-to-checkout.sample 2022-07-05 10:56:07.000000000 +0200 @@ -0,0 +1,78 @@ +#!/bin/sh + +# An example hook script to update a checked-out tree on a git push. +# +# This hook is invoked by git-receive-pack(1) when it reacts to git +# push and updates reference(s) in its repository, and when the push +# tries to update the branch that is currently checked out and the +# receive.denyCurrentBranch configuration variable is set to +# updateInstead. +# +# By default, such a push is refused if the working tree and the index +# of the remote repository has any difference from the currently +# checked out commit; when both the working tree and the index match +# the current commit, they are updated to match the newly pushed tip +# of the branch. This hook is to be used to override the default +# behaviour; however the code below reimplements the default behaviour +# as a starting point for convenient modification. +# +# The hook receives the commit with which the tip of the current +# branch is going to be updated: +commit=$1 + +# It can exit with a non-zero status to refuse the push (when it does +# so, it must not modify the index or the working tree). +die () { + echo >&2 "$*" + exit 1 +} + +# Or it can make any necessary changes to the working tree and to the +# index to bring them to the desired state when the tip of the current +# branch is updated to the new commit, and exit with a zero status. +# +# For example, the hook can simply run git read-tree -u -m HEAD "$1" +# in order to emulate git fetch that is run in the reverse direction +# with git push, as the two-tree form of git read-tree -u -m is +# essentially the same as git switch or git checkout that switches +# branches while keeping the local changes in the working tree that do +# not interfere with the difference between the branches. + +# The below is a more-or-less exact translation to shell of the C code +# for the default behaviour for git's push-to-checkout hook defined in +# the push_to_deploy() function in builtin/receive-pack.c. +# +# Note that the hook will be executed from the repository directory, +# not from the working tree, so if you want to perform operations on +# the working tree, you will have to adapt your code accordingly, e.g. +# by adding "cd .." or using relative paths. + +if ! git update-index -q --ignore-submodules --refresh +then + die "Up-to-date check failed" +fi + +if ! git diff-files --quiet --ignore-submodules -- +then + die "Working directory has unstaged changes" +fi + +# This is a rough translation of: +# +# head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX +if git cat-file -e HEAD 2>/dev/null +then + head=HEAD +else + head=$(git hash-object -t tree --stdin </dev/null) +fi + +if ! git diff-index --quiet --cached --ignore-submodules $head -- +then + die "Working directory has staged changes" +fi + +if ! git read-tree -u -m "$commit" +then + die "Could not update working tree to new HEAD" +fi diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jefferson-0.3+git.20160616/.git/hooks/update.sample new/jefferson-0.4.1+git.20220705/.git/hooks/update.sample --- old/jefferson-0.3+git.20160616/.git/hooks/update.sample 2016-06-16 16:41:06.000000000 +0200 +++ new/jefferson-0.4.1+git.20220705/.git/hooks/update.sample 2022-07-05 10:56:07.000000000 +0200 @@ -60,7 +60,7 @@ # --- Check types # if $newrev is 0000...0000, it's a commit to delete a ref. -zero="0000000000000000000000000000000000000000" +zero=$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0') if [ "$newrev" = "$zero" ]; then newrev_type=delete else Binary files old/jefferson-0.3+git.20160616/.git/index and new/jefferson-0.4.1+git.20220705/.git/index differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jefferson-0.3+git.20160616/.git/logs/HEAD new/jefferson-0.4.1+git.20220705/.git/logs/HEAD --- old/jefferson-0.3+git.20160616/.git/logs/HEAD 2016-06-16 16:41:06.000000000 +0200 +++ new/jefferson-0.4.1+git.20220705/.git/logs/HEAD 2022-07-05 10:56:07.000000000 +0200 @@ -1,2 +1,4 @@ -0000000000000000000000000000000000000000 6f9169bad3ceb4e212fae62ad710eeca3350226b Martin Hauke <mardnh@gmx.de> 1597349289 +0200 clone: from https://github.com/sviehb/jefferson.git -6f9169bad3ceb4e212fae62ad710eeca3350226b 6f9169bad3ceb4e212fae62ad710eeca3350226b Martin Hauke <mardnh@gmx.de> 1597349289 +0200 reset: moving to origin/master +0000000000000000000000000000000000000000 ecc51d7ab500e6286c80154f89388e3173784081 Martin Hauke <mardnh@gmx.de> 1665753391 +0200 clone: from https://github.com/sviehb/jefferson.git +ecc51d7ab500e6286c80154f89388e3173784081 ecc51d7ab500e6286c80154f89388e3173784081 Martin Hauke <mardnh@gmx.de> 1665753391 +0200 checkout: moving from master to master +ecc51d7ab500e6286c80154f89388e3173784081 ecc51d7ab500e6286c80154f89388e3173784081 Martin Hauke <mardnh@gmx.de> 1665753391 +0200 reset: moving to master +ecc51d7ab500e6286c80154f89388e3173784081 ecc51d7ab500e6286c80154f89388e3173784081 Martin Hauke <mardnh@gmx.de> 1665753450 +0200 reset: moving to master diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jefferson-0.3+git.20160616/.git/logs/refs/heads/master new/jefferson-0.4.1+git.20220705/.git/logs/refs/heads/master --- old/jefferson-0.3+git.20160616/.git/logs/refs/heads/master 2016-06-16 16:41:06.000000000 +0200 +++ new/jefferson-0.4.1+git.20220705/.git/logs/refs/heads/master 2022-07-05 10:56:07.000000000 +0200 @@ -1 +1 @@ -0000000000000000000000000000000000000000 6f9169bad3ceb4e212fae62ad710eeca3350226b Martin Hauke <mardnh@gmx.de> 1597349289 +0200 clone: from https://github.com/sviehb/jefferson.git +0000000000000000000000000000000000000000 ecc51d7ab500e6286c80154f89388e3173784081 Martin Hauke <mardnh@gmx.de> 1665753391 +0200 clone: from https://github.com/sviehb/jefferson.git diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jefferson-0.3+git.20160616/.git/logs/refs/remotes/origin/HEAD new/jefferson-0.4.1+git.20220705/.git/logs/refs/remotes/origin/HEAD --- old/jefferson-0.3+git.20160616/.git/logs/refs/remotes/origin/HEAD 2016-06-16 16:41:06.000000000 +0200 +++ new/jefferson-0.4.1+git.20220705/.git/logs/refs/remotes/origin/HEAD 2022-07-05 10:56:07.000000000 +0200 @@ -1 +1 @@ -0000000000000000000000000000000000000000 6f9169bad3ceb4e212fae62ad710eeca3350226b Martin Hauke <mardnh@gmx.de> 1597349289 +0200 clone: from https://github.com/sviehb/jefferson.git +0000000000000000000000000000000000000000 ecc51d7ab500e6286c80154f89388e3173784081 Martin Hauke <mardnh@gmx.de> 1665753391 +0200 clone: from https://github.com/sviehb/jefferson.git Binary files old/jefferson-0.3+git.20160616/.git/objects/pack/pack-3a0a0173eda603658094f7a412971bc5123e6e64.idx and new/jefferson-0.4.1+git.20220705/.git/objects/pack/pack-3a0a0173eda603658094f7a412971bc5123e6e64.idx differ Binary files old/jefferson-0.3+git.20160616/.git/objects/pack/pack-3a0a0173eda603658094f7a412971bc5123e6e64.pack and new/jefferson-0.4.1+git.20220705/.git/objects/pack/pack-3a0a0173eda603658094f7a412971bc5123e6e64.pack differ Binary files old/jefferson-0.3+git.20160616/.git/objects/pack/pack-725251cf98b7e590958c23630b690dc7a2d4860a.idx and new/jefferson-0.4.1+git.20220705/.git/objects/pack/pack-725251cf98b7e590958c23630b690dc7a2d4860a.idx differ Binary files old/jefferson-0.3+git.20160616/.git/objects/pack/pack-725251cf98b7e590958c23630b690dc7a2d4860a.pack and new/jefferson-0.4.1+git.20220705/.git/objects/pack/pack-725251cf98b7e590958c23630b690dc7a2d4860a.pack differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jefferson-0.3+git.20160616/.git/objects/pack/pack-725251cf98b7e590958c23630b690dc7a2d4860a.promisor new/jefferson-0.4.1+git.20220705/.git/objects/pack/pack-725251cf98b7e590958c23630b690dc7a2d4860a.promisor --- old/jefferson-0.3+git.20160616/.git/objects/pack/pack-725251cf98b7e590958c23630b690dc7a2d4860a.promisor 1970-01-01 01:00:00.000000000 +0100 +++ new/jefferson-0.4.1+git.20220705/.git/objects/pack/pack-725251cf98b7e590958c23630b690dc7a2d4860a.promisor 2022-07-05 10:56:07.000000000 +0200 @@ -0,0 +1,8 @@ +ecc51d7ab500e6286c80154f89388e3173784081 HEAD +5d8e2f0b2f4e6d0999382614f37981d6ca6c3d9f refs/heads/fix-inode-versioning +684069ea678c388d7fe58a61e671e4fa5a2a3c3f refs/heads/fix-lzo-python310 +da60d313db2be447b6f8c4ff2cdcca40292faf6d refs/heads/fix-path-traversal +ecc51d7ab500e6286c80154f89388e3173784081 refs/heads/master +3fafcf2c922aab5d72e2c65e862c6594e41dd700 refs/tags/v0.3 +dc8564fb90b54c71bd4c62eb71931fd5040fa308 refs/tags/v0.4 +93f4f7dce124fb4c9ffd1d497dc873013e86e355 refs/tags/v0.4.1 Binary files old/jefferson-0.3+git.20160616/.git/objects/pack/pack-c1640479d2ac5d8b1cc2ce8e37d65d4dba26e302.idx and new/jefferson-0.4.1+git.20220705/.git/objects/pack/pack-c1640479d2ac5d8b1cc2ce8e37d65d4dba26e302.idx differ Binary files old/jefferson-0.3+git.20160616/.git/objects/pack/pack-c1640479d2ac5d8b1cc2ce8e37d65d4dba26e302.pack and new/jefferson-0.4.1+git.20220705/.git/objects/pack/pack-c1640479d2ac5d8b1cc2ce8e37d65d4dba26e302.pack differ Binary files old/jefferson-0.3+git.20160616/.git/objects/pack/pack-d21226feb514c8682694542253e376efbcd4fdc8.idx and new/jefferson-0.4.1+git.20220705/.git/objects/pack/pack-d21226feb514c8682694542253e376efbcd4fdc8.idx differ Binary files old/jefferson-0.3+git.20160616/.git/objects/pack/pack-d21226feb514c8682694542253e376efbcd4fdc8.pack and new/jefferson-0.4.1+git.20220705/.git/objects/pack/pack-d21226feb514c8682694542253e376efbcd4fdc8.pack differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jefferson-0.3+git.20160616/.git/packed-refs new/jefferson-0.4.1+git.20220705/.git/packed-refs --- old/jefferson-0.3+git.20160616/.git/packed-refs 2016-06-16 16:41:06.000000000 +0200 +++ new/jefferson-0.4.1+git.20220705/.git/packed-refs 2022-07-05 10:56:07.000000000 +0200 @@ -1,2 +1,10 @@ # pack-refs with: peeled fully-peeled sorted -6f9169bad3ceb4e212fae62ad710eeca3350226b refs/remotes/origin/master +5d8e2f0b2f4e6d0999382614f37981d6ca6c3d9f refs/remotes/origin/fix-inode-versioning +684069ea678c388d7fe58a61e671e4fa5a2a3c3f refs/remotes/origin/fix-lzo-python310 +da60d313db2be447b6f8c4ff2cdcca40292faf6d refs/remotes/origin/fix-path-traversal +ecc51d7ab500e6286c80154f89388e3173784081 refs/remotes/origin/master +3fafcf2c922aab5d72e2c65e862c6594e41dd700 refs/tags/v0.3 +dc8564fb90b54c71bd4c62eb71931fd5040fa308 refs/tags/v0.4 +^361c6e38684bfb24e612e52726cf38c02bd2c9cc +93f4f7dce124fb4c9ffd1d497dc873013e86e355 refs/tags/v0.4.1 +^ecc51d7ab500e6286c80154f89388e3173784081 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jefferson-0.3+git.20160616/.git/refs/heads/master new/jefferson-0.4.1+git.20220705/.git/refs/heads/master --- old/jefferson-0.3+git.20160616/.git/refs/heads/master 2016-06-16 16:41:06.000000000 +0200 +++ new/jefferson-0.4.1+git.20220705/.git/refs/heads/master 2022-07-05 10:56:07.000000000 +0200 @@ -1 +1 @@ -6f9169bad3ceb4e212fae62ad710eeca3350226b +ecc51d7ab500e6286c80154f89388e3173784081 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jefferson-0.3+git.20160616/README.md new/jefferson-0.4.1+git.20220705/README.md --- old/jefferson-0.3+git.20160616/README.md 2016-06-16 16:41:06.000000000 +0200 +++ new/jefferson-0.4.1+git.20220705/README.md 2022-07-05 10:56:07.000000000 +0200 @@ -1,33 +1,31 @@ -# jefferson -JFFS2 filesystem extraction tool +## Jefferson -Installation -============ -```bash -$ sudo python setup.py install -``` +JFFS2 filesystem extraction tool +### Installation -Dependencies -============ -- `cstruct` -- `pyliblzma` +Follow these steps on Debian based systems (Debian, Ubuntu, Kali, ...) to perform a system-wide installation of jefferon: ```bash -$ sudo pip install cstruct -$ sudo apt-get install python-lzma +git clone https://github.com/sviehb/jefferson.git +cd jefferson +sudo apt update +sudo apt install python3-pip liblzo2-dev +sudo python3 -m pip install -r requirements.txt +sudo python3 setup.py install ``` -Features -============ -- Big/Little Endian support -- `JFFS2_COMPR_ZLIB`, `JFFS2_COMPR_RTIME`, and `JFFS2_COMPR_LZMA` compression support + +### Features + +- big-endian and little-endian support with auto-detection +- zlib, rtime, LZMA, and LZO compression support - CRC checks - for now only enforced on `hdr_crc` -- Extraction of symlinks, directories, files, and device nodes -- Detection/handling of duplicate inode numbers. Occurs if multiple JFFS2 filesystems are found in one file and causes `jefferson` to treat segments as separate filesystems +- extraction of symlinks, directories, files, and device nodes +- detection/handling of duplicate inode numbers. Occurs if multiple JFFS2 filesystems are found in one file and causes `jefferson` to treat segments as separate filesystems + +### Usage -Usage -============ ```bash $ jefferson filesystem.img -d outdir ``` diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jefferson-0.3+git.20160616/requirements.txt new/jefferson-0.4.1+git.20220705/requirements.txt --- old/jefferson-0.3+git.20160616/requirements.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/jefferson-0.4.1+git.20220705/requirements.txt 2022-07-05 10:56:07.000000000 +0200 @@ -0,0 +1,2 @@ +cstruct==2.1 +python-lzo==1.14 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jefferson-0.3+git.20160616/setup.py new/jefferson-0.4.1+git.20220705/setup.py --- old/jefferson-0.3+git.20160616/setup.py 2016-06-16 16:41:06.000000000 +0200 +++ new/jefferson-0.4.1+git.20220705/setup.py 2022-07-05 10:56:07.000000000 +0200 @@ -1,20 +1,19 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # coding=utf-8 from distutils.core import setup -version = '0.2' +version = "0.4.1" setup( - name='jefferson', + name="jefferson", version=version, - description='', - author='Stefan Viehb��ck', - url='https://github.com/sviehb/jefferson', - license='MIT', - + description="", + author="Stefan Viehb��ck", + url="https://github.com/sviehb/jefferson", + license="MIT", requires=['cstruct'], - packages=['jefferson'], - package_dir={'jefferson': 'src/jefferson'}, - scripts=['src/scripts/jefferson'], -) \ No newline at end of file + packages=["jefferson"], + package_dir={"jefferson": "src/jefferson"}, + scripts=["src/scripts/jefferson"], +) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jefferson-0.3+git.20160616/src/jefferson/__init__.py new/jefferson-0.4.1+git.20220705/src/jefferson/__init__.py --- old/jefferson-0.3+git.20160616/src/jefferson/__init__.py 2016-06-16 16:41:06.000000000 +0200 +++ new/jefferson-0.4.1+git.20220705/src/jefferson/__init__.py 2022-07-05 10:56:07.000000000 +0200 @@ -1 +1 @@ -__author__ = 'stefan' +__author__ = "stefan" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jefferson-0.3+git.20160616/src/jefferson/jffs2_lzma.py new/jefferson-0.4.1+git.20220705/src/jefferson/jffs2_lzma.py --- old/jefferson-0.3+git.20160616/src/jefferson/jffs2_lzma.py 2016-06-16 16:41:06.000000000 +0200 +++ new/jefferson-0.4.1+git.20220705/src/jefferson/jffs2_lzma.py 2022-07-05 10:56:07.000000000 +0200 @@ -15,7 +15,7 @@ def decompress(data, outlen): - lzma_header = struct.pack('<BIQ', PROPERTIES, DICT_SIZE, outlen) + lzma_header = struct.pack("<BIQ", PROPERTIES, DICT_SIZE, outlen) lzma_data = lzma_header + data decompressed = lzma.decompress(lzma_data) - return decompressed \ No newline at end of file + return decompressed diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jefferson-0.3+git.20160616/src/jefferson/rtime.py new/jefferson-0.4.1+git.20220705/src/jefferson/rtime.py --- old/jefferson-0.3+git.20160616/src/jefferson/rtime.py 2016-06-16 16:41:06.000000000 +0200 +++ new/jefferson-0.4.1+git.20220705/src/jefferson/rtime.py 2022-07-05 10:56:07.000000000 +0200 @@ -4,11 +4,11 @@ outpos = 0 pos = 0 while outpos < destlen: - value = ord(data_in[pos]) + value = data_in[pos] pos += 1 cpage_out[outpos] = value outpos += 1 - repeat = ord(data_in[pos]) + repeat = data_in[pos] pos += 1 backoffs = positions[value] @@ -21,6 +21,8 @@ backoffs += 1 repeat -= 1 else: - cpage_out[outpos:outpos + repeat] = cpage_out[backoffs:backoffs + repeat] + cpage_out[outpos : outpos + repeat] = cpage_out[ + backoffs : backoffs + repeat + ] outpos += repeat - return str(cpage_out) \ No newline at end of file + return cpage_out \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jefferson-0.3+git.20160616/src/scripts/jefferson new/jefferson-0.4.1+git.20220705/src/scripts/jefferson --- old/jefferson-0.3+git.20160616/src/scripts/jefferson 2016-06-16 16:41:06.000000000 +0200 +++ new/jefferson-0.4.1+git.20220705/src/scripts/jefferson 2022-07-05 10:56:07.000000000 +0200 @@ -1,20 +1,25 @@ -#! /usr/bin/python2 +#!/usr/bin/env python3 +import argparse import struct import stat import os +import sys import zlib import binascii - +import lzo +import mmap +import contextlib import cstruct from jefferson import jffs2_lzma, rtime def PAD(x): - return (((x) + 3) & ~3) + return ((x) + 3) & ~3 +JFFS2_OLD_MAGIC_BITMASK = 0x1984 JFFS2_MAGIC_BITMASK = 0x1985 JFFS2_COMPR_NONE = 0x00 JFFS2_COMPR_ZERO = 0x01 @@ -27,10 +32,10 @@ JFFS2_COMPR_LZMA = 0x08 # /* Compatibility flags. */ -JFFS2_COMPAT_MASK = 0xc000 # /* What do to if an unknown nodetype is found */ +JFFS2_COMPAT_MASK = 0xC000 # /* What do to if an unknown nodetype is found */ JFFS2_NODE_ACCURATE = 0x2000 # /* INCOMPAT: Fail to mount the filesystem */ -JFFS2_FEATURE_INCOMPAT = 0xc000 +JFFS2_FEATURE_INCOMPAT = 0xC000 # /* ROCOMPAT: Mount read-only */ JFFS2_FEATURE_ROCOMPAT = 0x8000 # /* RWCOMPAT_COPY: Mount read/write, and copy the node when it's GC'd */ @@ -46,309 +51,289 @@ JFFS2_NODETYPE_XATTR = JFFS2_FEATURE_INCOMPAT | JFFS2_NODE_ACCURATE | 8 JFFS2_NODETYPE_XREF = JFFS2_FEATURE_INCOMPAT | JFFS2_NODE_ACCURATE | 9 - def mtd_crc(data): - return (binascii.crc32(data, -1) ^ -1) & 0xffffffff - + return (binascii.crc32(data, -1) ^ -1) & 0xFFFFFFFF -cstruct.typedef('uint8', 'uint8_t') -cstruct.typedef('uint16', 'jint16_t') -cstruct.typedef('uint32', 'jint32_t') -cstruct.typedef('uint32', 'jmode_t') +def is_safe_path(basedir, real_path): + basedir = os.path.realpath(basedir) + return basedir == os.path.commonpath((basedir, real_path)) + +cstruct.typedef("uint8", "uint8_t") +cstruct.typedef("uint16", "jint16_t") +cstruct.typedef("uint32", "jint32_t") +cstruct.typedef("uint32", "jmode_t") class Jffs2_unknown_node(cstruct.CStruct): __byte_order__ = cstruct.LITTLE_ENDIAN - __struct__ = """ - /* All start like this */ - jint16_t magic; - jint16_t nodetype; - jint32_t totlen; /* So we can skip over nodes we don't grok */ - jint32_t hdr_crc; + __def__ = """ + struct { + /* All start like this */ + jint16_t magic; + jint16_t nodetype; + jint32_t totlen; /* So we can skip over nodes we don't grok */ + jint32_t hdr_crc; + } """ def unpack(self, data): - cstruct.CStruct.unpack(self, data[:self.size]) - comp_hrd_crc = mtd_crc(data[:self.size - 4]) + cstruct.CStruct.unpack(self, data[: self.size]) + comp_hrd_crc = mtd_crc(data[: self.size - 4]) if comp_hrd_crc == self.hdr_crc: self.hdr_crc_match = True else: - #print 'hdr_crc does not match!' + # print("hdr_crc does not match!") self.hdr_crc_match = False -class Jffs2_raw_xattr(cstruct.CStruct): - __byte_order__ = cstruct.LITTLE_ENDIAN - __struct__ = """ - jint16_t magic; - jint16_t nodetype; /* = JFFS2_NODETYPE_XATTR */ - jint32_t totlen; - jint32_t hdr_crc; - jint32_t xid; /* XATTR identifier number */ - jint32_t version; - uint8_t xprefix; - uint8_t name_len; - jint16_t value_len; - jint32_t data_crc; - jint32_t node_crc; - uint8_t data[0]; - """ - - -class Jffs2_raw_summary(cstruct.CStruct): - __byte_order__ = cstruct.LITTLE_ENDIAN - __struct__ = """ - jint16_t magic; - jint16_t nodetype; /* = JFFS2_NODETYPE_SUMMARY */ - jint32_t totlen; - jint32_t hdr_crc; - jint32_t sum_num; /* number of sum entries*/ - jint32_t cln_mkr; /* clean marker size, 0 = no cleanmarker */ - jint32_t padded; /* sum of the size of padding nodes */ - jint32_t sum_crc; /* summary information crc */ - jint32_t node_crc; /* node crc */ - jint32_t sum[0]; /* inode summary info */ - """ - - -class Jffs2_raw_xref(cstruct.CStruct): - __byte_order__ = cstruct.LITTLE_ENDIAN - __struct__ = """ - jint16_t magic; - jint16_t nodetype; /* = JFFS2_NODETYPE_XREF */ - jint32_t totlen; - jint32_t hdr_crc; - jint32_t ino; /* inode number */ - jint32_t xid; /* XATTR identifier number */ - jint32_t xseqno; /* xref sequencial number */ - jint32_t node_crc; - """ - - class Jffs2_raw_dirent(cstruct.CStruct): __byte_order__ = cstruct.LITTLE_ENDIAN - __struct__ = """ - jint16_t magic; - jint16_t nodetype; /* == JFFS2_NODETYPE_DIRENT */ - jint32_t totlen; - jint32_t hdr_crc; - jint32_t pino; - jint32_t version; - jint32_t ino; /* == zero for unlink */ - jint32_t mctime; - uint8_t nsize; - uint8_t type; - uint8_t unused[2]; - jint32_t node_crc; - jint32_t name_crc; - /* uint8_t data[0]; -> name */ + __def__ = """ + struct { + jint16_t magic; + jint16_t nodetype; /* == JFFS2_NODETYPE_DIRENT */ + jint32_t totlen; + jint32_t hdr_crc; + jint32_t pino; + jint32_t version; + jint32_t ino; /* == zero for unlink */ + jint32_t mctime; + uint8_t nsize; + uint8_t type; + uint8_t unused[2]; + jint32_t node_crc; + jint32_t name_crc; + /* uint8_t data[0]; -> name */ + } """ def unpack(self, data, node_offset): - cstruct.CStruct.unpack(self, data[:self.size]) - self.name = data[self.size:self.size + self.nsize] + cstruct.CStruct.unpack(self, data[: self.size]) + self.name = data[self.size : self.size + self.nsize].tobytes() self.node_offset = node_offset - if mtd_crc(data[:self.size - 8]) == self.node_crc: + if mtd_crc(data[: self.size - 8]) == self.node_crc: self.node_crc_match = True else: - print 'node_crc does not match!' + print("node_crc does not match!") self.node_crc_match = False if mtd_crc(self.name) == self.name_crc: self.name_crc_match = True else: - print 'data_crc does not match!' + print("data_crc does not match!") self.name_crc_match = False def __str__(self): result = [] - for field in self.__fields__ + ['name', 'node_offset']: + for field in self.__fields__ + ["name", "node_offset"]: result.append(field + "=" + str(getattr(self, field, None))) return type(self).__name__ + "(" + ", ".join(result) + ")" class Jffs2_raw_inode(cstruct.CStruct): __byte_order__ = cstruct.LITTLE_ENDIAN - __struct__ = """ - jint16_t magic; /* A constant magic number. */ - jint16_t nodetype; /* == JFFS2_NODETYPE_INODE */ - jint32_t totlen; /* Total length of this node (inc data, etc.) */ - jint32_t hdr_crc; - jint32_t ino; /* Inode number. */ - jint32_t version; /* Version number. */ - jmode_t mode; /* The file's type or mode. */ - jint16_t uid; /* The file's owner. */ - jint16_t gid; /* The file's group. */ - jint32_t isize; /* Total resultant size of this inode (used for truncations) */ - jint32_t atime; /* Last access time. */ - jint32_t mtime; /* Last modification time. */ - jint32_t ctime; /* Change time. */ - jint32_t offset; /* Where to begin to write. */ - jint32_t csize; /* (Compressed) data size */ - jint32_t dsize; /* Size of the node's data. (after decompression) */ - uint8_t compr; /* Compression algorithm used */ - uint8_t usercompr; /* Compression algorithm requested by the user */ - jint16_t flags; /* See JFFS2_INO_FLAG_* */ - jint32_t data_crc; /* CRC for the (compressed) data. */ - jint32_t node_crc; /* CRC for the raw inode (excluding data) */ - /* uint8_t data[0]; */ + __def__ = """ + struct { + jint16_t magic; /* A constant magic number. */ + jint16_t nodetype; /* == JFFS2_NODETYPE_INODE */ + jint32_t totlen; /* Total length of this node (inc data, etc.) */ + jint32_t hdr_crc; + jint32_t ino; /* Inode number. */ + jint32_t version; /* Version number. */ + jmode_t mode; /* The file's type or mode. */ + jint16_t uid; /* The file's owner. */ + jint16_t gid; /* The file's group. */ + jint32_t isize; /* Total resultant size of this inode (used for truncations) */ + jint32_t atime; /* Last access time. */ + jint32_t mtime; /* Last modification time. */ + jint32_t ctime; /* Change time. */ + jint32_t offset; /* Where to begin to write. */ + jint32_t csize; /* (Compressed) data size */ + jint32_t dsize; /* Size of the node's data. (after decompression) */ + uint8_t compr; /* Compression algorithm used */ + uint8_t usercompr; /* Compression algorithm requested by the user */ + jint16_t flags; /* See JFFS2_INO_FLAG_* */ + jint32_t data_crc; /* CRC for the (compressed) data. */ + jint32_t node_crc; /* CRC for the raw inode (excluding data) */ + /* uint8_t data[0]; */ + } """ def unpack(self, data): - cstruct.CStruct.unpack(self, data[:self.size]) + cstruct.CStruct.unpack(self, data[: self.size]) - node_data = data[self.size:self.size + self.csize] - if self.compr == JFFS2_COMPR_NONE: - self.data = node_data - elif self.compr == JFFS2_COMPR_ZERO: - self.data = '\x00' * self.dsize - elif self.compr == JFFS2_COMPR_ZLIB: - self.data = zlib.decompress(node_data) - elif self.compr == JFFS2_COMPR_RTIME: - self.data = rtime.decompress(node_data, self.dsize) - elif self.compr == JFFS2_COMPR_LZMA: - self.data = jffs2_lzma.decompress(node_data, self.dsize) - else: - print 'compression not implemented', self - print node_data.encode('hex')[:20] - self.data = node_data + node_data = data[self.size : self.size + self.csize].tobytes() + try: + if self.compr == JFFS2_COMPR_NONE: + self.data = node_data + elif self.compr == JFFS2_COMPR_ZERO: + self.data = b"\x00" * self.dsize + elif self.compr == JFFS2_COMPR_ZLIB: + self.data = zlib.decompress(node_data) + elif self.compr == JFFS2_COMPR_RTIME: + self.data = rtime.decompress(node_data, self.dsize) + elif self.compr == JFFS2_COMPR_LZMA: + self.data = jffs2_lzma.decompress(node_data, self.dsize) + elif self.compr == JFFS2_COMPR_LZO: + self.data = lzo.decompress(node_data, False, self.dsize) + else: + print("compression not implemented", self) + print(node_data.hex()[:20]) + self.data = node_data + except Exception as e: + print("Decompression error on inode {}: {}".format(self.ino, e), file=sys.stderr) + self.data = b"\x00" * self.dsize if len(self.data) != self.dsize: - print 'data length mismatch!' + print("data length mismatch!") - if mtd_crc(data[:self.size - 8]) == self.node_crc: + if mtd_crc(data[: self.size - 8]) == self.node_crc: self.node_crc_match = True else: - print 'hdr_crc does not match!' + print("hdr_crc does not match!") self.node_crc_match = False if mtd_crc(node_data) == self.data_crc: self.data_crc_match = True else: - print 'data_crc does not match!' + print("data_crc does not match!") self.data_crc_match = False + class Jffs2_device_node_old(cstruct.CStruct): __byte_order__ = cstruct.LITTLE_ENDIAN - __struct__ = """ - jint16_t old_id; + __def__ = """ + struct { + jint16_t old_id; + } """ + class Jffs2_device_node_new(cstruct.CStruct): __byte_order__ = cstruct.LITTLE_ENDIAN - __struct__ = """ - jint32_t new_id; + __def__ = """ + struct { + jint32_t new_id; + } """ + NODETYPES = { JFFS2_FEATURE_INCOMPAT: Jffs2_unknown_node, JFFS2_NODETYPE_DIRENT: Jffs2_raw_dirent, JFFS2_NODETYPE_INODE: Jffs2_raw_inode, - JFFS2_NODETYPE_CLEANMARKER: 'JFFS2_NODETYPE_CLEANMARKER', - JFFS2_NODETYPE_SUMMARY: Jffs2_raw_summary, - JFFS2_NODETYPE_XATTR: Jffs2_raw_xattr, - JFFS2_NODETYPE_XREF: Jffs2_raw_xref, - JFFS2_NODETYPE_PADDING: 'JFFS2_NODETYPE_PADDING' + JFFS2_NODETYPE_CLEANMARKER: "JFFS2_NODETYPE_CLEANMARKER", + JFFS2_NODETYPE_PADDING: "JFFS2_NODETYPE_PADDING", } def set_endianness(endianness): - Jffs2_device_node_new.__fmt__ = endianness + Jffs2_device_node_new.__fmt__[1:] - Jffs2_device_node_old.__fmt__ = endianness + Jffs2_device_node_old.__fmt__[1:] + global Jffs2_device_node_new, Jffs2_device_node_old, Jffs2_unknown_node, Jffs2_raw_dirent, Jffs2_raw_inode, Jffs2_raw_summary, Jffs2_raw_xattr, Jffs2_raw_xref - for node in NODETYPES.values(): - if isinstance(node, cstruct.CStructMeta): - node.__fmt__ = endianness + node.__fmt__[1:] + Jffs2_device_node_new = Jffs2_device_node_new.parse( + Jffs2_device_node_new.__def__, + __name__=Jffs2_device_node_new.__name__, + __byte_order__=endianness, + ) + + Jffs2_device_node_old = Jffs2_device_node_old.parse( + Jffs2_device_node_old.__def__, + __name__=Jffs2_device_node_old.__name__, + __byte_order__=endianness, + ) + + Jffs2_unknown_node = Jffs2_unknown_node.parse( + Jffs2_unknown_node.__def__, + __name__=Jffs2_unknown_node.__name__, + __byte_order__=endianness, + ) + + Jffs2_raw_dirent = Jffs2_raw_dirent.parse( + Jffs2_raw_dirent.__def__, + __name__=Jffs2_raw_dirent.__name__, + __byte_order__=endianness, + ) + + Jffs2_raw_inode = Jffs2_raw_inode.parse( + Jffs2_raw_inode.__def__, + __name__=Jffs2_raw_inode.__name__, + __byte_order__=endianness, + ) def scan_fs(content, endianness, verbose=False): - set_endianness(endianness) - summaries = [] pos = 0 - jffs2_magic_bitmask_str = struct.pack(endianness + 'H', JFFS2_MAGIC_BITMASK) - fs_index = 0 + jffs2_old_magic_bitmask_str = struct.pack(endianness + "H", JFFS2_OLD_MAGIC_BITMASK) + jffs2_magic_bitmask_str = struct.pack(endianness + "H", JFFS2_MAGIC_BITMASK) + content_mv = memoryview(content) fs = {} - fs[fs_index] = {} - fs[fs_index]["endianness"] = endianness - fs[fs_index][JFFS2_NODETYPE_INODE] = [] - fs[fs_index][JFFS2_NODETYPE_DIRENT] = [] - fs[fs_index][JFFS2_NODETYPE_XATTR] = [] - fs[fs_index][JFFS2_NODETYPE_XREF] = [] - fs[fs_index][JFFS2_NODETYPE_SUMMARY] = [] + fs[JFFS2_NODETYPE_INODE] = {} + fs[JFFS2_NODETYPE_DIRENT] = {} - dirent_dict = {} while True: - find_result = content.find(jffs2_magic_bitmask_str, pos, len(content) - Jffs2_unknown_node.size) - if find_result == -1: + find_result = content.find( + jffs2_magic_bitmask_str, pos, len(content) - Jffs2_unknown_node.size + ) + find_result_old = content.find( + jffs2_old_magic_bitmask_str, pos, len(content) - Jffs2_unknown_node.size + ) + if find_result == -1 and find_result_old == -1: break - else: + if find_result != -1: pos = find_result + else: + pos = find_result_old unknown_node = Jffs2_unknown_node() - unknown_node.unpack(content[pos:pos + unknown_node.size]) + unknown_node.unpack(content_mv[pos : pos + unknown_node.size]) if not unknown_node.hdr_crc_match: pos += 1 continue offset = pos pos += PAD(unknown_node.totlen) - if unknown_node.magic == JFFS2_MAGIC_BITMASK: + if unknown_node.magic in [ + JFFS2_MAGIC_BITMASK, + JFFS2_OLD_MAGIC_BITMASK, + ]: if unknown_node.nodetype in NODETYPES: if unknown_node.nodetype == JFFS2_NODETYPE_DIRENT: dirent = Jffs2_raw_dirent() - dirent.unpack(content[0 + offset:], offset) - if dirent.ino in dirent_dict: - print 'duplicate inode use detected!!!' - fs_index += 1 - fs[fs_index] = {} - fs[fs_index]["endianness"] = endianness - fs[fs_index][JFFS2_NODETYPE_INODE] = [] - fs[fs_index][JFFS2_NODETYPE_DIRENT] = [] - fs[fs_index][JFFS2_NODETYPE_XATTR] = [] - fs[fs_index][JFFS2_NODETYPE_XREF] = [] - fs[fs_index][JFFS2_NODETYPE_SUMMARY] = [] - dirent_dict = {} - - dirent_dict[dirent.ino] = dirent - - fs[fs_index][JFFS2_NODETYPE_DIRENT].append(dirent) + dirent.unpack(content_mv[0 + offset :], offset) + if dirent.ino in fs[JFFS2_NODETYPE_DIRENT]: + if dirent.version > fs[JFFS2_NODETYPE_DIRENT][dirent.ino].version: + fs[JFFS2_NODETYPE_DIRENT][dirent.ino] = dirent + else: + fs[JFFS2_NODETYPE_DIRENT][dirent.ino] = dirent if verbose: - print '0x%08X:' % (offset), dirent + print("0x%08X:" % (offset), dirent) elif unknown_node.nodetype == JFFS2_NODETYPE_INODE: inode = Jffs2_raw_inode() - inode.unpack(content[0 + offset:]) - fs[fs_index][JFFS2_NODETYPE_INODE].append(inode) - if verbose: - print '0x%08X:' % (offset), inode - elif unknown_node.nodetype == JFFS2_NODETYPE_XREF: - xref = Jffs2_raw_xref() - xref.unpack(content[offset:offset + xref.size]) - fs[fs_index][JFFS2_NODETYPE_XREF].append(xref) - if verbose: - print '0x%08X:' % (offset), xref - elif unknown_node.nodetype == JFFS2_NODETYPE_XATTR: - xattr = Jffs2_raw_xattr() - xattr.unpack(content[offset:offset + xattr.size]) - fs[fs_index][JFFS2_NODETYPE_XREF].append(xattr) - if verbose: - print '0x%08X:' % (offset), xattr - elif unknown_node.nodetype == JFFS2_NODETYPE_SUMMARY: - summary = Jffs2_raw_summary() - summary.unpack(content[offset:offset + summary.size]) - summaries.append(summary) - fs[fs_index][JFFS2_NODETYPE_SUMMARY].append(summary) + inode.unpack(content_mv[0 + offset :]) + + if inode.ino in fs[JFFS2_NODETYPE_INODE]: + fs[JFFS2_NODETYPE_INODE][inode.ino].append(inode) + else: + fs[JFFS2_NODETYPE_INODE][inode.ino] = [inode] if verbose: - print '0x%08X:' % (offset), summary + print("0x%08X:" % (offset), inode) elif unknown_node.nodetype == JFFS2_NODETYPE_CLEANMARKER: pass elif unknown_node.nodetype == JFFS2_NODETYPE_PADDING: pass + elif unknown_node.nodetype == JFFS2_NODETYPE_SUMMARY: + pass + elif unknown_node.nodetype == JFFS2_NODETYPE_XATTR: + pass + elif unknown_node.nodetype == JFFS2_NODETYPE_XREF: + pass else: - print 'Unhandled node type', unknown_node.nodetype, unknown_node - return fs.values() + print("Unknown node type", unknown_node.nodetype, unknown_node) + content_mv.release() + return fs def get_device(inode): @@ -358,32 +343,34 @@ if inode.dsize == len(Jffs2_device_node_new): node = Jffs2_device_node_new() node.unpack(inode.data) - return os.makedev((node.new_id & 0xfff00) >> 8, (node.new_id & 0xff) | ((node.new_id >> 12) & 0xfff00)) - elif inode.dsize == len(Jffs2_device_node_old): + return os.makedev( + (node.new_id & 0xFFF00) >> 8, + (node.new_id & 0xFF) | ((node.new_id >> 12) & 0xFFF00), + ) + + if inode.dsize == len(Jffs2_device_node_old): node = Jffs2_device_node_old() node.unpack(inode.data) - return os.makedev((node.old_id >> 8) & 0xff, node.old_id & 0xff) + return os.makedev((node.old_id >> 8) & 0xFF, node.old_id & 0xFF) return None +def sort_version(item): + return item.version def dump_fs(fs, target): node_dict = {} - set_endianness(fs["endianness"]) - - for dirent in fs[JFFS2_NODETYPE_DIRENT]: + for dirent in fs[JFFS2_NODETYPE_DIRENT].values(): dirent.inodes = [] - for inode in fs[JFFS2_NODETYPE_INODE]: - if inode.ino == dirent.ino: - dirent.inodes.append(inode) - if dirent.ino in node_dict: - print 'duplicate dirent.ino use detected!!!', dirent + for ino, inodes in fs[JFFS2_NODETYPE_INODE].items(): + if ino == dirent.ino: + dirent.inodes = sorted(inodes, key=sort_version) node_dict[dirent.ino] = dirent - for dirent in fs[JFFS2_NODETYPE_DIRENT]: + for dirent in fs[JFFS2_NODETYPE_DIRENT].values(): pnode_pino = dirent.pino pnodes = [] - for i in range(100): + for _ in range(100): if pnode_pino not in node_dict: break pnode = node_dict[pnode_pino] @@ -394,105 +381,110 @@ node_names = [] for pnode in pnodes: - node_names.append(pnode.name) - node_names.append(dirent.name) - path = '/'.join(node_names) + node_names.append(pnode.name.decode()) + node_names.append(dirent.name.decode()) + path = "/".join(node_names) + + target_path = os.path.realpath(os.path.join(target, path)) + + if not is_safe_path(target, target_path): + print(f"Path traversal attempt to {target_path}, discarding.") + continue - target_path = os.path.join(os.getcwd(), target, path) for inode in dirent.inodes: try: if stat.S_ISDIR(inode.mode): - print 'writing S_ISDIR', path + print("writing S_ISDIR", path) if not os.path.isdir(target_path): os.makedirs(target_path) elif stat.S_ISLNK(inode.mode): - print 'writing S_ISLNK', path + print("writing S_ISLNK", path) if not os.path.islink(target_path): if os.path.exists(target_path): - print 'file already exists as', inode.data continue os.symlink(inode.data, target_path) elif stat.S_ISREG(inode.mode): - print 'writing S_ISREG', path + print("writing S_ISREG", path) if not os.path.isfile(target_path): if not os.path.isdir(os.path.dirname(target_path)): os.makedirs(os.path.dirname(target_path)) - with open(target_path, 'wb') as fd: + with open(target_path, "wb") as fd: for inode in dirent.inodes: fd.seek(inode.offset) fd.write(inode.data) os.chmod(target_path, stat.S_IMODE(inode.mode)) break elif stat.S_ISCHR(inode.mode): - print 'writing S_ISBLK', path + print("writing S_ISBLK", path) os.mknod(target_path, inode.mode, get_device(inode)) elif stat.S_ISBLK(inode.mode): - print 'writing S_ISBLK', path + print("writing S_ISBLK", path) os.mknod(target_path, inode.mode, get_device(inode)) elif stat.S_ISFIFO(inode.mode): - print 'skipping S_ISFIFO', path + print("skipping S_ISFIFO", path) elif stat.S_ISSOCK(inode.mode): - print 'skipping S_ISSOCK', path + print("skipping S_ISSOCK", path) else: - print 'unhandled inode.mode: %o' % inode.mode, inode, dirent - - except IOError as e: - print "I/O error(%i): %s" % (e.errno, e.strerror), inode, dirent + print("unhandled inode.mode: %o" % inode.mode, inode, dirent) - except OSError as e: - print "OS error(%i): %s" % (e.errno, e.strerror), inode, dirent + except OSError as error: + print("OS error(%i): %s" % (error.errno, error.strerror), inode, dirent) def main(): - import argparse parser = argparse.ArgumentParser() - parser.add_argument('-v', '--verbose', help='increase output verbosity', - action="store_true") - parser.add_argument('-f', '--force', help='overwrite destination directory', - action="store_true") - parser.add_argument('filesystem', type=str, - help="path to filesystem") - parser.add_argument('-d', '--dest', type=str, default='jffs2-root', - help='destination directory (default: jffs-root)') + parser.add_argument( + "-v", "--verbose", help="increase output verbosity", action="store_true" + ) + parser.add_argument( + "-f", "--force", help="overwrite destination directory", action="store_true" + ) + parser.add_argument("filesystem", type=str, help="path to filesystem") + parser.add_argument( + "-d", + "--dest", + type=str, + default="jffs2-root", + help="destination directory (default: jffs-root)", + ) args = parser.parse_args() dest_path = os.path.join(os.getcwd(), args.dest) if os.path.exists(dest_path): if not args.force: - print 'Destination path already exists!' + print("Destination path already exists!") return else: os.mkdir(dest_path) - content = open(args.filesystem, 'rb').read() - fs_list = scan_fs(content, cstruct.BIG_ENDIAN, verbose=args.verbose) - fs_list += scan_fs(content, cstruct.LITTLE_ENDIAN, verbose=args.verbose) - - fs_index = 1 - for fs in fs_list: - if not fs[JFFS2_NODETYPE_DIRENT]: - continue + with contextlib.ExitStack() as context_stack: + filesystem = context_stack.enter_context(open(args.filesystem, "rb")) + filesystem_len = os.fstat(filesystem.fileno()).st_size + if 0 == filesystem_len: + return + content = context_stack.enter_context( + mmap.mmap(filesystem.fileno(), filesystem_len, access=mmap.ACCESS_READ) + ) + magic = struct.unpack("<H", content[0:2])[0] + if magic in [JFFS2_OLD_MAGIC_BITMASK, JFFS2_MAGIC_BITMASK]: + endianness = cstruct.LITTLE_ENDIAN + else: + endianness = cstruct.BIG_ENDIAN + + set_endianness(endianness) + + fs = scan_fs(content, endianness, verbose=args.verbose) + print("dumping fs to %s (endianness: %s)" % (dest_path, endianness)) + for key, value in fs.items(): + print("%s count: %i" % (NODETYPES[key].__name__, len(value))) + + if not os.path.exists(dest_path): + os.mkdir(dest_path) - dest_path_fs = os.path.join(dest_path, 'fs_%i' % fs_index) - print 'dumping fs #%i to %s' % (fs_index, dest_path_fs) - for key, value in fs.iteritems(): - if key == "endianness": - if value == cstruct.BIG_ENDIAN: - print 'Endianness: Big' - elif value == cstruct.LITTLE_ENDIAN: - print 'Endianness: Little' - continue - - print '%s count: %i' % (NODETYPES[key].__name__, len(value)) - - if not os.path.exists(dest_path_fs): - os.mkdir(dest_path_fs) - - dump_fs(fs, dest_path_fs) - print '-' * 10 - fs_index += 1 + dump_fs(fs, dest_path) + print("-" * 10) -if __name__ == '__main__': +if __name__ == "__main__": main()
participants (1)
-
Source-Sync