Skip to content

Commit 15c4501

Browse files
authored
fix: strip android native build ids for fdroid (#1140)
* fix: strip android native build ids for fdroid * fix * fix * fix
1 parent 25952c5 commit 15c4501

2 files changed

Lines changed: 275 additions & 11 deletions

File tree

.github/workflows/release.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,6 @@ jobs:
3636
curl -u ${{ secrets.BASIC_AUTH }} -o android/app/app.key ${{ secrets.URL_PREFIX }}app.key
3737
curl -u ${{ secrets.BASIC_AUTH }} -o android/key.properties ${{ secrets.URL_PREFIX }}key.properties
3838
- name: Build
39-
env:
40-
LDFLAGS: -Wl,--build-id=none
4139
run: dart run fl_build -p android
4240
- name: Verify F-Droid native libraries
4341
run: scripts/release/verify-fdroid-native-libs.sh

scripts/release/verify-fdroid-native-libs.sh

Lines changed: 275 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
55
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
66
APK_DIR="${APK_DIR:-$REPO_ROOT/build/app/outputs/flutter-apk}"
77
APP_NAME="${APP_NAME:-ServerBox}"
8+
KEY_PROPERTIES="${KEY_PROPERTIES:-$REPO_ROOT/android/key.properties}"
89

910
require_cmd() {
1011
local name="$1"
@@ -14,16 +15,240 @@ require_cmd() {
1415
fi
1516
}
1617

18+
android_sdk_roots() {
19+
local candidates=(
20+
"${ANDROID_HOME:-}"
21+
"${ANDROID_SDK_ROOT:-}"
22+
"$HOME/Library/Android/sdk"
23+
"$HOME/Android/Sdk"
24+
"/opt/android-sdk"
25+
"/usr/local/lib/android/sdk"
26+
)
27+
local roots=''
28+
local candidate
29+
30+
for candidate in "${candidates[@]}"; do
31+
if [[ -z "$candidate" || ! -d "$candidate" ]]; then
32+
continue
33+
fi
34+
35+
if printf '%s' "$roots" | grep -Fxq "$candidate"; then
36+
continue
37+
fi
38+
roots+="$candidate"$'\n'
39+
done
40+
41+
printf '%s' "$roots"
42+
}
43+
44+
android_tool_version() {
45+
local path="$1"
46+
local version='0'
47+
case "$path" in
48+
*/build-tools/*)
49+
version="${path#*/build-tools/}"
50+
version="${version%%/*}"
51+
;;
52+
*/ndk/*)
53+
version="${path#*/ndk/}"
54+
version="${version%%/*}"
55+
;;
56+
esac
57+
58+
printf '%s\n' "$version"
59+
}
60+
61+
version_sort_key() {
62+
local version="$1"
63+
local part
64+
local padded
65+
local key=''
66+
67+
IFS='.' read -ra parts <<< "$version"
68+
for part in "${parts[@]}"; do
69+
if [[ "$part" =~ ^[0-9]+$ ]]; then
70+
printf -v padded '%010d' "$part"
71+
else
72+
padded="$part"
73+
fi
74+
key+="$padded."
75+
done
76+
77+
printf '%s\n' "$key"
78+
}
79+
80+
select_latest_android_path() {
81+
local path
82+
local version
83+
local key
84+
85+
while IFS= read -r path; do
86+
if [[ -z "$path" ]]; then
87+
continue
88+
fi
89+
version="$(android_tool_version "$path")"
90+
key="$(version_sort_key "$version")"
91+
printf '%s\t%s\n' "$key" "$path"
92+
done |
93+
sort |
94+
tail -n 1 |
95+
while IFS=$'\t' read -r _ path; do
96+
printf '%s\n' "$path"
97+
done
98+
}
99+
100+
find_latest_android_tool() {
101+
local name="$1"
102+
local sdk_root
103+
local matches=()
104+
local path
105+
106+
while IFS= read -r sdk_root; do
107+
if [[ -z "$sdk_root" || ! -d "$sdk_root/build-tools" ]]; then
108+
continue
109+
fi
110+
while IFS= read -r path; do
111+
matches+=("$path")
112+
done < <(find "$sdk_root/build-tools" -type f -name "$name")
113+
done < <(android_sdk_roots)
114+
115+
if [[ ${#matches[@]} -eq 0 ]]; then
116+
return 1
117+
fi
118+
119+
printf '%s\n' "${matches[@]}" | select_latest_android_path
120+
}
121+
122+
find_llvm_objcopy() {
123+
local sdk_root
124+
local matches=()
125+
local path
126+
127+
while IFS= read -r sdk_root; do
128+
if [[ -z "$sdk_root" || ! -d "$sdk_root/ndk" ]]; then
129+
continue
130+
fi
131+
while IFS= read -r path; do
132+
matches+=("$path")
133+
done < <(find "$sdk_root/ndk" -type f -path '*/toolchains/llvm/prebuilt/*/bin/llvm-objcopy')
134+
done < <(android_sdk_roots)
135+
136+
if [[ ${#matches[@]} -ne 0 ]]; then
137+
printf '%s\n' "${matches[@]}" | select_latest_android_path
138+
return
139+
fi
140+
141+
command -v llvm-objcopy || command -v objcopy
142+
}
143+
144+
find_readelf() {
145+
local sdk_root
146+
local matches=()
147+
local path
148+
149+
while IFS= read -r sdk_root; do
150+
if [[ -z "$sdk_root" || ! -d "$sdk_root/ndk" ]]; then
151+
continue
152+
fi
153+
while IFS= read -r path; do
154+
matches+=("$path")
155+
done < <(find "$sdk_root/ndk" -type f -path '*/toolchains/llvm/prebuilt/*/bin/llvm-readelf')
156+
done < <(android_sdk_roots)
157+
158+
if [[ ${#matches[@]} -ne 0 ]]; then
159+
printf '%s\n' "${matches[@]}" | select_latest_android_path
160+
return
161+
fi
162+
163+
command -v readelf || command -v llvm-readelf
164+
}
165+
166+
property_value() {
167+
local name="$1"
168+
grep -E "^[[:space:]]*${name}[[:space:]]*=" "$KEY_PROPERTIES" |
169+
tail -n 1 |
170+
sed -E 's/^[^=]*=[[:space:]]*//; s/[[:space:]]*$//' || true
171+
}
172+
173+
has_build_id() {
174+
local so_file="$1"
175+
"$READELF" -n "$so_file" 2>/dev/null | grep -q 'Build ID'
176+
}
177+
17178
require_cmd find
18-
require_cmd readelf
179+
require_cmd grep
180+
require_cmd sed
181+
require_cmd sort
19182
require_cmd unzip
183+
require_cmd zip
184+
185+
OBJCOPY="${OBJCOPY:-}"
186+
READELF="${READELF:-}"
187+
APKSIGNER="${APKSIGNER:-}"
188+
ZIPALIGN="${ZIPALIGN:-}"
189+
190+
if [[ -z "$OBJCOPY" ]]; then
191+
OBJCOPY="$(find_llvm_objcopy || true)"
192+
fi
193+
if [[ -z "$READELF" ]]; then
194+
READELF="$(find_readelf || true)"
195+
fi
196+
if [[ -z "$APKSIGNER" ]]; then
197+
APKSIGNER="$(find_latest_android_tool apksigner || true)"
198+
fi
199+
if [[ -z "$ZIPALIGN" ]]; then
200+
ZIPALIGN="$(find_latest_android_tool zipalign || true)"
201+
fi
202+
203+
if [[ -z "$OBJCOPY" || ! -x "$OBJCOPY" ]]; then
204+
echo 'llvm-objcopy or objcopy is required to remove ELF build-id notes' >&2
205+
exit 1
206+
fi
207+
208+
if [[ -z "$READELF" || ! -x "$READELF" ]]; then
209+
echo 'readelf or llvm-readelf is required to inspect ELF build-id notes' >&2
210+
exit 1
211+
fi
212+
213+
if [[ -z "$APKSIGNER" || ! -x "$APKSIGNER" ]]; then
214+
echo 'Android build-tools apksigner is required to re-sign patched APKs' >&2
215+
exit 1
216+
fi
217+
218+
if [[ -z "$ZIPALIGN" || ! -x "$ZIPALIGN" ]]; then
219+
echo 'Android build-tools zipalign is required before re-signing patched APKs' >&2
220+
exit 1
221+
fi
222+
223+
if [[ ! -f "$KEY_PROPERTIES" ]]; then
224+
echo "key.properties not found: $KEY_PROPERTIES" >&2
225+
exit 1
226+
fi
227+
228+
store_file="$(property_value storeFile)"
229+
store_password="$(property_value storePassword)"
230+
key_alias="$(property_value keyAlias)"
231+
key_password="$(property_value keyPassword)"
232+
233+
if [[ -z "$store_file" || -z "$store_password" || -z "$key_alias" || -z "$key_password" ]]; then
234+
echo "storeFile, storePassword, keyAlias, and keyPassword are required in $KEY_PROPERTIES" >&2
235+
exit 1
236+
fi
237+
238+
if [[ "$store_file" != /* ]]; then
239+
store_file="$REPO_ROOT/android/app/$store_file"
240+
fi
241+
242+
if [[ ! -f "$store_file" ]]; then
243+
echo "signing store file not found: $store_file" >&2
244+
exit 1
245+
fi
20246

21247
shopt -s nullglob
22248

23249
tmp_dir="$(mktemp -d)"
24250
trap 'rm -rf "$tmp_dir"' EXIT
25251

26-
failures=()
27252
apks=()
28253
patterns=(
29254
"${APP_NAME}_*_arm64.apk"
@@ -52,18 +277,59 @@ for apk in "${apks[@]}"; do
52277
extract_dir="$tmp_dir/$apk_name"
53278
mkdir -p "$extract_dir"
54279
unzip -qq "$apk" "lib/*/*.so" -d "$extract_dir"
280+
patched_files=()
55281

56282
while IFS= read -r so_file; do
57-
if readelf -n "$so_file" | grep -q 'Build ID'; then
58-
failures+=("$apk_name:${so_file#$extract_dir/}")
283+
if has_build_id "$so_file"; then
284+
rel_path="${so_file#"$extract_dir"/}"
285+
tmp_so="$so_file.tmp"
286+
"$OBJCOPY" --remove-section=.note.gnu.build-id "$so_file" "$tmp_so"
287+
mv "$tmp_so" "$so_file"
288+
if has_build_id "$so_file"; then
289+
echo "failed to remove ELF build-id note: $apk_name:$rel_path" >&2
290+
exit 1
291+
fi
292+
patched_files+=("$rel_path")
59293
fi
60294
done < <(find "$extract_dir" -type f -name '*.so' | sort)
295+
296+
if [[ ${#patched_files[@]} -eq 0 ]]; then
297+
continue
298+
fi
299+
300+
echo "Removing ELF build-id notes from $apk_name"
301+
(
302+
cd "$extract_dir"
303+
zip -q -0 "$apk" "${patched_files[@]}"
304+
)
305+
306+
aligned_apk="$tmp_dir/$apk_name.aligned.apk"
307+
"$ZIPALIGN" -f -p 4 "$apk" "$aligned_apk"
308+
mv "$aligned_apk" "$apk"
309+
310+
"$APKSIGNER" sign \
311+
--ks "$store_file" \
312+
--ks-key-alias "$key_alias" \
313+
--ks-pass "pass:$store_password" \
314+
--key-pass "pass:$key_password" \
315+
"$apk"
61316
done
62317

63-
if [[ ${#failures[@]} -ne 0 ]]; then
64-
echo 'native libraries still contain ELF build-id notes:' >&2
65-
printf ' %s\n' "${failures[@]}" >&2
66-
exit 1
67-
fi
318+
for apk in "${apks[@]}"; do
319+
apk_name="$(basename "$apk")"
320+
verify_dir="$tmp_dir/verify-$apk_name"
321+
mkdir -p "$verify_dir"
322+
unzip -qq "$apk" "lib/*/*.so" -d "$verify_dir"
323+
324+
while IFS= read -r so_file; do
325+
if has_build_id "$so_file"; then
326+
rel_path="${so_file#"$verify_dir"/}"
327+
echo "native library still contains ELF build-id note: $apk_name:$rel_path" >&2
328+
exit 1
329+
fi
330+
done < <(find "$verify_dir" -type f -name '*.so' | sort)
331+
332+
"$APKSIGNER" verify "$apk"
333+
done
68334

69335
echo 'F-Droid native library verification passed.'

0 commit comments

Comments
 (0)