Summary
KPM v0.2.2 can treat a truncated artifact download as successful and continue into installation. In the observed case, the UI eventually printed Installed 1 package(s) successfully, but KOReader was not installed and no installed_packages row was created.
Environment
- Device: Kindle Paperwhite 3 (7th generation / PW3)
- Firmware: 5.16.2.1.1
- KPM: v0.2.2 (CLI v1.0.0)
- Platform reported by KPM:
kindlepw2
- Package:
koreader_2026.7.2_kindlepw2.kpkg
Steps and observed result
- Ran
;kpm update successfully.
- Ran
;kpm search koreader successfully.
- Ran
;kpm install koreader over Wi-Fi.
- KPM printed
Installed 1 package(s) successfully.
- No
/mnt/us/koreader/ directory or /mnt/us/documents/KOReader.sh scriptlet existed afterward.
- The
installed_packages table contained only KPM, not KOReader.
- KPM's temporary artifact remained at:
/mnt/us/kmc/kpm/packages/tmp/koreader_2026.7.2_kindlepw2.kpkg
The retained artifact was 32,515,584 bytes. gzip -t failed with unexpected end of file, and tar reported truncated input.
At the time of diagnosis, the server returned Content-Length: 41700710 for the same artifact. A fresh download on the Mac was 41,700,710 bytes, passed gzip -t, contained 1,214 tar entries, and had SHA-256:
d4a933338bbca7c69c9400e2104f5a0af65240d71406e624a65264a841d8a29d
Copying that complete package to /mnt/us/ and invoking KPM locally from a scriptlet succeeded. KPM returned 0, created the KOReader directory and scriptlet, and inserted the KOReader 2026.7.2 row.
Relevant source behavior
On current main at ffa767fffadd731bd59f2bca8c83231f4fc0ab2d, src/install.c calls curl_easy_perform(curl) without retaining or checking the returned CURLcode, then only rejects HTTP response codes >= 400:
|
curl_easy_setopt(curl, CURLOPT_URL, target_url); |
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, Internal_DownloadWriteCallback); |
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &download_data); // This _should_ work |
|
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, Internal_DownloadHeaderCallback); |
|
curl_easy_setopt(curl, CURLOPT_HEADERDATA, &download_data); |
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); //@FIXME @TODO: Wth? |
|
curl_easy_setopt(curl, CURLOPT_USERAGENT, "kpm/1.0.0"); |
|
curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS); |
|
curl_easy_setopt(curl, CURLOPT_FTP_SKIP_PASV_IP, 1L); |
|
curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L); |
|
curl_easy_perform(curl); |
|
long response_code; |
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); |
|
close(fd); |
|
curl_easy_cleanup(curl); |
|
|
|
if (response_code >= 400) |
|
{ |
|
if (strncmp("file://", target_url, 7) == 0) |
|
{ |
|
free(target_url); |
|
kpmIO->log(KPM_VERBOSITY_DEBUG, "artifact is a file:// URL, ignoring error checking"); // @TODO |
|
} |
|
else |
|
{ |
|
free(target_url); |
|
kpmIO->log(KPM_VERBOSITY_ERROR, "Curl received an invalid response code: %zu", response_code); |
|
return KPM_CURL_ERROR; |
|
} |
|
} |
The code then calculates a SHA-256 string but does not compare it with package metadata (and the current repository manifest does not appear to publish an artifact digest):
|
// Check the digest |
|
unsigned char md[EVP_MAX_MD_SIZE]; |
|
unsigned int md_len; |
|
EVP_DigestFinal_ex(download_data.evp_ctx, md, &md_len); |
|
EVP_MD_CTX_destroy(download_data.evp_ctx); |
|
|
|
char* hash_str = malloc(md_len*2 + 1); |
|
hash_str[0] = 0; |
|
for (int md_i = 0; md_i < md_len; md_i++) |
|
{ |
|
sprintf(hash_str, "%s%x", hash_str, md[md_i]); |
|
} |
|
kpmIO->log(KPM_VERBOSITY_DEBUG, "Calculated hash for file: %s", hash_str); |
|
free(hash_str); |
|
|
|
free(target_url); |
Expected behavior
- Check and propagate the
CURLcode from curl_easy_perform.
- Reject partial transfers such as
CURLE_PARTIAL_FILE and remove or clearly quarantine the incomplete temporary artifact.
- Do not print a successful package count when extraction/hooks did not produce a completed install and database record.
- Ideally publish and verify artifact SHA-256 values, or at minimum verify the received byte count when
Content-Length is available.
I can provide the small text diagnostic logs if useful. No device serial number or account data is involved.
Summary
KPM v0.2.2 can treat a truncated artifact download as successful and continue into installation. In the observed case, the UI eventually printed
Installed 1 package(s) successfully, but KOReader was not installed and noinstalled_packagesrow was created.Environment
kindlepw2koreader_2026.7.2_kindlepw2.kpkgSteps and observed result
;kpm updatesuccessfully.;kpm search koreadersuccessfully.;kpm install koreaderover Wi-Fi.Installed 1 package(s) successfully./mnt/us/koreader/directory or/mnt/us/documents/KOReader.shscriptlet existed afterward.installed_packagestable contained only KPM, not KOReader./mnt/us/kmc/kpm/packages/tmp/koreader_2026.7.2_kindlepw2.kpkgThe retained artifact was 32,515,584 bytes.
gzip -tfailed withunexpected end of file, andtarreported truncated input.At the time of diagnosis, the server returned
Content-Length: 41700710for the same artifact. A fresh download on the Mac was 41,700,710 bytes, passedgzip -t, contained 1,214 tar entries, and had SHA-256:d4a933338bbca7c69c9400e2104f5a0af65240d71406e624a65264a841d8a29dCopying that complete package to
/mnt/us/and invoking KPM locally from a scriptlet succeeded. KPM returned 0, created the KOReader directory and scriptlet, and inserted the KOReader 2026.7.2 row.Relevant source behavior
On current
mainatffa767fffadd731bd59f2bca8c83231f4fc0ab2d,src/install.ccallscurl_easy_perform(curl)without retaining or checking the returnedCURLcode, then only rejects HTTP response codes >= 400:KPM/src/install.c
Lines 438 to 467 in ffa767f
The code then calculates a SHA-256 string but does not compare it with package metadata (and the current repository manifest does not appear to publish an artifact digest):
KPM/src/install.c
Lines 469 to 484 in ffa767f
Expected behavior
CURLcodefromcurl_easy_perform.CURLE_PARTIAL_FILEand remove or clearly quarantine the incomplete temporary artifact.Content-Lengthis available.I can provide the small text diagnostic logs if useful. No device serial number or account data is involved.