From 04c45e7b916da06f86bf883db26672eeb5e09c8b Mon Sep 17 00:00:00 2001 From: Xavier Roche Date: Fri, 17 Jul 2026 14:57:51 +0200 Subject: [PATCH 1/2] app: write the emergency dump somewhere it can actually land emergencyDump() passed Environment.getExternalStorageState() to new File(). That returns the status string, "mounted", so the dump went to the relative path mounted/HTTrack/error.txt, resolved against the process working directory of /, where FileWriter threw straight into an empty catch. It has never written a file, on any release. The catch now logs, since silence is what hid this. Being static, it had no Context to ask for a directory, which is likely how it drifted; it takes the Runner's parent activity instead, and writes into our own external directory, falling back to the internal one. A crash path must not depend on a permission or on a volume being mounted, and neither does that. The JNI glue had the same hole: assertion failures fell back to a hardcoded /mnt/sdcard/Download/HTTrack/error.txt, a layout gone since KitKat, whose fopen has been failing quietly ever since. Once initRootPath() has run, emergencyLog already points into the mirror root; before it, there is nowhere we may write, and logcat has the assertion either way. MKDIR_MODE went with it, having no users. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: Xavier Roche --- .../com/httrack/android/HTTrackActivity.java | 29 ++++++++++++------- app/src/main/jni/htslibjni.c | 16 +++++----- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/com/httrack/android/HTTrackActivity.java b/app/src/main/java/com/httrack/android/HTTrackActivity.java index a9e5c56..97eb54b 100755 --- a/app/src/main/java/com/httrack/android/HTTrackActivity.java +++ b/app/src/main/java/com/httrack/android/HTTrackActivity.java @@ -891,18 +891,25 @@ protected static boolean isIPv6Enabled() { } /** - * Emergency dump. - */ - protected static void emergencyDump(final Throwable e) { - try { - final File dumpFile = new File(new File( - Environment.getExternalStorageState(), "HTTrack"), "error.txt"); - final FileWriter writer = new FileWriter(dumpFile); - final PrintWriter print = new PrintWriter(writer); + * Emergency dump, into storage of our own: this runs on a crash path, so it must not depend + * on a permission, nor on a volume being mounted. + * + * @param context + * null while the activity is being torn down, in which case nothing is written + * @param e + * the throwable to record + */ + protected static void emergencyDump(final Context context, final Throwable e) { + if (context == null) { + return; + } + final File external = context.getExternalFilesDir(null); + final File dumpFile = new File( + external != null ? external : context.getFilesDir(), "error.txt"); + try (final PrintWriter print = new PrintWriter(new FileWriter(dumpFile))) { e.printStackTrace(print); - writer.close(); - HTTrackActivity.setFileReadWrite(dumpFile); } catch (final IOException io) { + Log.w(HTTrackActivity.class.getSimpleName(), "could not write " + dumpFile, io); } } @@ -1106,7 +1113,7 @@ protected Void doInBackground(final Void... arg0) { try { runInternal(); } catch (final RuntimeException e) { - HTTrackActivity.emergencyDump(e); + HTTrackActivity.emergencyDump(parent, e); throw e; } finally { ended = true; diff --git a/app/src/main/jni/htslibjni.c b/app/src/main/jni/htslibjni.c index 0884d5b..ef517c2 100755 --- a/app/src/main/jni/htslibjni.c +++ b/app/src/main/jni/htslibjni.c @@ -62,15 +62,13 @@ static char *rootPath = NULL; /* log assertion failure. */ static void log_assert_failure(const char* exp, const char* file, int line) { __android_log_print(ANDROID_LOG_VERBOSE, "httrack", "assertion '%s' failed at %s:%d", exp, file, line); -#define MKDIR_MODE (S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) - /* FIXME TODO: pass the getExternalStorageDirectory() in init. */ - FILE *dumpFile; - const char *const filename = emergencyLog != NULL ? emergencyLog - : "/mnt/sdcard/Download/HTTrack/error.txt"; - dumpFile = fopen(filename, "wb"); - if (dumpFile != NULL) { - fprintf(dumpFile, "assertion '%s' failed at %s:%d\n", exp, file, line); - fclose(dumpFile); + /* Nothing is writable before initRootPath(); logcat above carries it either way. */ + if (emergencyLog != NULL) { + FILE *const dumpFile = fopen(emergencyLog, "wb"); + if (dumpFile != NULL) { + fprintf(dumpFile, "assertion '%s' failed at %s:%d\n", exp, file, line); + fclose(dumpFile); + } } } From 3642b6fe651824d4e39c99718ad65de6231b1532 Mon Sep 17 00:00:00 2001 From: Xavier Roche Date: Sat, 18 Jul 2026 09:53:11 +0200 Subject: [PATCH 2/2] native: create the emergency log owner-only CodeQL flagged the emergency-log fopen as world-writable creation: fopen's default mode is 0666, so the crash log could be world-readable and -writable. The alert is new only because this PR already rewrites these lines; the pattern predates it. open() with O_CREAT and 0600 fixes it, and fdopen keeps the rest as it was. The sibling stdio-redirect fopen in initRootPath has the same shape but is a separate concern, untouched here and left to its own change. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: Xavier Roche --- app/src/main/jni/htslibjni.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/app/src/main/jni/htslibjni.c b/app/src/main/jni/htslibjni.c index ef517c2..9ac4d2a 100755 --- a/app/src/main/jni/htslibjni.c +++ b/app/src/main/jni/htslibjni.c @@ -29,6 +29,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include #include +#include +#include #include @@ -64,10 +66,16 @@ static void log_assert_failure(const char* exp, const char* file, int line) { __android_log_print(ANDROID_LOG_VERBOSE, "httrack", "assertion '%s' failed at %s:%d", exp, file, line); /* Nothing is writable before initRootPath(); logcat above carries it either way. */ if (emergencyLog != NULL) { - FILE *const dumpFile = fopen(emergencyLog, "wb"); - if (dumpFile != NULL) { - fprintf(dumpFile, "assertion '%s' failed at %s:%d\n", exp, file, line); - fclose(dumpFile); + /* Owner-only (0600): a crash log created with fopen's default 0666 is world-writable. */ + const int fd = open(emergencyLog, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + if (fd != -1) { + FILE *const dumpFile = fdopen(fd, "wb"); + if (dumpFile != NULL) { + fprintf(dumpFile, "assertion '%s' failed at %s:%d\n", exp, file, line); + fclose(dumpFile); + } else { + close(fd); + } } } }