diff --git a/.cirrus.yml b/.cirrus.yml index d0a5939f44921..ffdda4dbba8ad 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -63,6 +63,14 @@ task: env: FILE_ENV: "./ci/test/00_setup_env_arm.sh" +task: + name: 'Win32 [GOAL: deploy] [unit tests, no gui, no boost::process, no functional tests]' + << : *GLOBAL_TASK_TEMPLATE + container: + image: ubuntu:bionic + env: + FILE_ENV: "./ci/test/00_setup_env_win32.sh" + task: name: 'Win64 [GOAL: deploy] [unit tests, no gui, no boost::process, no functional tests]' << : *GLOBAL_TASK_TEMPLATE diff --git a/.travis.yml b/.travis.yml index f03c8d697b0f4..de84fab4b7439 100644 --- a/.travis.yml +++ b/.travis.yml @@ -65,6 +65,11 @@ jobs: script: - set -o errexit; source ./ci/lint/06_script.sh + - stage: test + name: 'Win32 [GOAL: deploy] [unit tests, no gui, no boost::process, no functional tests]' + env: >- + FILE_ENV="./ci/test/00_setup_env_win32.sh" + - stage: test name: 'PowerPC64 [GOAL: install] [unit tests, functional tests]' os: linux diff --git a/Makefile.am b/Makefile.am index c8af4228f35c7..29690f5658a25 100644 --- a/Makefile.am +++ b/Makefile.am @@ -25,7 +25,7 @@ BITCOIN_QT_BIN=$(top_builddir)/src/qt/$(BITCOIN_GUI_NAME)$(EXEEXT) BITCOIN_CLI_BIN=$(top_builddir)/src/$(BITCOIN_CLI_NAME)$(EXEEXT) BITCOIN_TX_BIN=$(top_builddir)/src/$(BITCOIN_TX_NAME)$(EXEEXT) BITCOIN_WALLET_BIN=$(top_builddir)/src/$(BITCOIN_WALLET_TOOL_NAME)$(EXEEXT) -BITCOIN_WIN_INSTALLER=$(PACKAGE)-$(PACKAGE_VERSION)-win64-setup$(EXEEXT) +BITCOIN_WIN_INSTALLER=$(PACKAGE)-$(PACKAGE_VERSION)-win$(WINDOWS_BITS)-setup$(EXEEXT) empty := space := $(empty) $(empty) @@ -355,7 +355,12 @@ if TARGET_DARWIN $(AM_V_at) $(PYTHON) $(top_srcdir)/contrib/devtools/test-security-check.py TestSecurityChecks.test_MACHO endif if TARGET_WINDOWS + ifeq ($(WINDOWS_BITS),32) + $(AM_V_at) $(PYTHON) $(top_srcdir)/contrib/devtools/test-security-check.py TestSecurityChecks.test_32bit_PE + endif + ifeq ($(WINDOWS_BITS),64) $(AM_V_at) $(PYTHON) $(top_srcdir)/contrib/devtools/test-security-check.py TestSecurityChecks.test_PE + endif endif if TARGET_LINUX $(AM_V_at) $(PYTHON) $(top_srcdir)/contrib/devtools/test-security-check.py TestSecurityChecks.test_ELF diff --git a/ci/test/00_setup_env_win32.sh b/ci/test/00_setup_env_win32.sh new file mode 100644 index 0000000000000..436e067130f2a --- /dev/null +++ b/ci/test/00_setup_env_win32.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2019-2020 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +export LC_ALL=C.UTF-8 + +export CONTAINER_NAME=ci_win32 +export DOCKER_NAME_TAG=ubuntu:18.04 # Check that bionic can cross-compile to win64 (bionic is used in the gitian build as well) +export HOST=i686-w64-mingw32 +export DPKG_ADD_ARCH="i386" +export PACKAGES="python3 nsis g++-mingw-w64-i686 wine-binfmt wine32 file" +export RUN_FUNCTIONAL_TESTS=false +export RUN_SECURITY_TESTS="true" +export GOAL="deploy" +export BITCOIN_CONFIG="--enable-reduce-exports --disable-gui-tests --without-boost-process" + +# Compiler for MinGW-w64 causes false -Wreturn-type warning. +# See https://sourceforge.net/p/mingw-w64/bugs/306/ +export NO_WERROR=1 diff --git a/ci/test/wrap-wine.sh b/ci/test/wrap-wine.sh index 58a8983e6ec6c..d70d5c99b2c9f 100755 --- a/ci/test/wrap-wine.sh +++ b/ci/test/wrap-wine.sh @@ -9,11 +9,16 @@ export LC_ALL=C.UTF-8 for b_name in {"${BASE_OUTDIR}/bin"/*,src/secp256k1/*tests,src/univalue/{no_nul,test_json,unitester,object}}.exe; do # shellcheck disable=SC2044 for b in $(find "${BASE_ROOT_DIR}" -executable -type f -name "$(basename $b_name)"); do - if (file "$b" | grep "Windows"); then + filetype="$(file -b "$b")" + if grep -q "Windows" <<<"${filetype}"; then echo "Wrap $b ..." mv "$b" "${b}_orig" echo '#!/usr/bin/env bash' > "$b" + if grep -q "\b64\b" <<<"${filetype}"; then echo "wine64 \"${b}_orig\" \"\$@\"" >> "$b" + else + echo "wine \"${b}_orig\" \"\$@\"" >> "$b" + fi chmod +x "$b" fi done diff --git a/configure.ac b/configure.ac index 1857eb8fc8778..85fe4ce1b6d03 100644 --- a/configure.ac +++ b/configure.ac @@ -602,20 +602,20 @@ case $host in *mingw*) TARGET_OS=windows AC_CHECK_LIB([mingwthrd],[main],, AC_MSG_ERROR(libmingwthrd missing)) - AC_CHECK_LIB([kernel32], [GetModuleFileNameA],, AC_MSG_ERROR(libkernel32 missing)) + AC_CHECK_LIB([kernel32], [main],, AC_MSG_ERROR(libkernel32 missing)) AC_CHECK_LIB([user32], [main],, AC_MSG_ERROR(libuser32 missing)) AC_CHECK_LIB([gdi32], [main],, AC_MSG_ERROR(libgdi32 missing)) AC_CHECK_LIB([comdlg32], [main],, AC_MSG_ERROR(libcomdlg32 missing)) AC_CHECK_LIB([winmm], [main],, AC_MSG_ERROR(libwinmm missing)) - AC_CHECK_LIB([shell32], [SHGetSpecialFolderPathW],, AC_MSG_ERROR(libshell32 missing)) + AC_CHECK_LIB([shell32], [main],, AC_MSG_ERROR(libshell32 missing)) AC_CHECK_LIB([comctl32], [main],, AC_MSG_ERROR(libcomctl32 missing)) - AC_CHECK_LIB([ole32], [CoCreateInstance],, AC_MSG_ERROR(libole32 missing)) + AC_CHECK_LIB([ole32], [main],, AC_MSG_ERROR(libole32 missing)) AC_CHECK_LIB([oleaut32], [main],, AC_MSG_ERROR(liboleaut32 missing)) AC_CHECK_LIB([uuid], [main],, AC_MSG_ERROR(libuuid missing)) - AC_CHECK_LIB([advapi32], [CryptAcquireContextW],, AC_MSG_ERROR(libadvapi32 missing)) - AC_CHECK_LIB([ws2_32], [WSAStartup],, AC_MSG_ERROR(libws2_32 missing)) - AC_CHECK_LIB([shlwapi], [PathRemoveFileSpecW],, AC_MSG_ERROR(libshlwapi missing)) - AC_CHECK_LIB([iphlpapi], [GetAdaptersAddresses],, AC_MSG_ERROR(libiphlpapi missing)) + AC_CHECK_LIB([advapi32], [main],, AC_MSG_ERROR(libadvapi32 missing)) + AC_CHECK_LIB([ws2_32], [main],, AC_MSG_ERROR(libws2_32 missing)) + AC_CHECK_LIB([shlwapi], [main],, AC_MSG_ERROR(libshlwapi missing)) + AC_CHECK_LIB([iphlpapi], [main],, AC_MSG_ERROR(libiphlpapi missing)) dnl -static is interpreted by libtool, where it has a different meaning. dnl In libtool-speak, it's -all-static. @@ -632,6 +632,12 @@ case $host in fi CPPFLAGS="$CPPFLAGS -D_MT -DWIN32 -D_WINDOWS -DBOOST_THREAD_USE_LIB -D_WIN32_WINNT=0x0601 -D_WIN32_IE=0x0501 -DWIN32_LEAN_AND_MEAN" + case $host in + i?86-*) WINDOWS_BITS=32 ;; + x86_64-*) WINDOWS_BITS=64 ;; + *) AC_MSG_ERROR("Could not determine win32/win64 for installer") ;; + esac + AC_SUBST(WINDOWS_BITS) dnl libtool insists upon adding -nostdlib and a list of objects/libs to link against. dnl That breaks our ability to build dll's with static libgcc/libstdc++/libssp. Override @@ -783,6 +789,8 @@ if test x$ac_cv_sys_large_files != x && CPPFLAGS="$CPPFLAGS -D_LARGE_FILES=$ac_cv_sys_large_files" fi +AX_CHECK_LINK_FLAG([[-Wl,--large-address-aware]], [LDFLAGS="$LDFLAGS -Wl,--large-address-aware"]) + AX_GCC_FUNC_ATTRIBUTE([visibility]) AX_GCC_FUNC_ATTRIBUTE([dllexport]) AX_GCC_FUNC_ATTRIBUTE([dllimport]) diff --git a/contrib/devtools/security-check.py b/contrib/devtools/security-check.py index 02615edb547f8..2769831103fd8 100755 --- a/contrib/devtools/security-check.py +++ b/contrib/devtools/security-check.py @@ -194,14 +194,25 @@ def check_ELF_separate_code(executable): def get_PE_dll_characteristics(executable) -> int: '''Get PE DllCharacteristics bits''' + return get_PE_dll_arch_and_characteristics(executable)[1] + +def get_PE_dll_arch_and_characteristics(executable): + ''' + Get PE DllCharacteristics bits. + Returns a tuple (arch,bits) where arch is 'i386:x86-64' or 'i386' + and bits is the DllCharacteristics value. + ''' stdout = run_command([OBJDUMP_CMD, '-x', executable]) + arch = '' bits = 0 for line in stdout.splitlines(): tokens = line.split() + if len(tokens) >= 2 and tokens[0] == 'architecture:': + arch = tokens[1].rstrip(',') if len(tokens)>=2 and tokens[0] == 'DllCharacteristics': bits = int(tokens[1],16) - return bits + return (arch,bits) IMAGE_DLL_CHARACTERISTICS_HIGH_ENTROPY_VA = 0x0020 IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE = 0x0040 @@ -212,12 +223,18 @@ def check_PE_DYNAMIC_BASE(executable) -> bool: bits = get_PE_dll_characteristics(executable) return (bits & IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE) == IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE +# (64-bit only:) # Must support high-entropy 64-bit address space layout randomization # in addition to DYNAMIC_BASE to have secure ASLR. def check_PE_HIGH_ENTROPY_VA(executable) -> bool: '''PIE: DllCharacteristics bit 0x20 signifies high-entropy ASLR''' - bits = get_PE_dll_characteristics(executable) - return (bits & IMAGE_DLL_CHARACTERISTICS_HIGH_ENTROPY_VA) == IMAGE_DLL_CHARACTERISTICS_HIGH_ENTROPY_VA + (arch,bits) = get_PE_dll_arch_and_characteristics(executable) + if arch == 'i386:x86-64': + reqbits = IMAGE_DLL_CHARACTERISTICS_HIGH_ENTROPY_VA + else: # Unnecessary on 32-bit + assert(arch == 'i386') + reqbits = 0 + return (bits & reqbits) == reqbits def check_PE_RELOC_SECTION(executable) -> bool: '''Check for a reloc section. This is required for functional ASLR.''' diff --git a/contrib/devtools/test-security-check.py b/contrib/devtools/test-security-check.py index ec2d886653421..5f23687eeb35b 100755 --- a/contrib/devtools/test-security-check.py +++ b/contrib/devtools/test-security-check.py @@ -44,6 +44,21 @@ def test_ELF(self): self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-znoexecstack','-fstack-protector-all','-Wl,-zrelro','-Wl,-z,now','-pie','-fPIE', '-Wl,-z,separate-code']), (0, '')) + def test_32bit_PE(self): + source = 'test1.c' + executable = 'test1.exe' + cc = 'i686-w64-mingw32-gcc' + write_testcode(source) + + self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--no-nxcompat','-Wl,--no-dynamicbase','-no-pie','-fno-PIE']), + (1, executable+': failed DYNAMIC_BASE NX RELOC_SECTION')) + self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--nxcompat','-Wl,--no-dynamicbase','-no-pie','-fno-PIE']), + (1, executable+': failed DYNAMIC_BASE RELOC_SECTION')) + self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--nxcompat','-Wl,--dynamicbase','-no-pie','-fno-PIE']), + (1, executable+': failed RELOC_SECTION')) + self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--nxcompat','-Wl,--dynamicbase','-pie','-fPIE']), + (0, '')) + def test_PE(self): source = 'test1.c' executable = 'test1.exe' diff --git a/contrib/gitian-descriptors/gitian-win.yml b/contrib/gitian-descriptors/gitian-win.yml index 75533975b0922..e1f7a7bd91eaf 100644 --- a/contrib/gitian-descriptors/gitian-win.yml +++ b/contrib/gitian-descriptors/gitian-win.yml @@ -30,7 +30,7 @@ script: | set -e -o pipefail WRAP_DIR=$HOME/wrapped - HOSTS="x86_64-w64-mingw32" + HOSTS="i686-w64-mingw32 x86_64-w64-mingw32" CONFIGFLAGS="--enable-reduce-exports --disable-bench --disable-gui-tests" FAKETIME_HOST_PROGS="ar ranlib nm windres strip objcopy" FAKETIME_PROGS="date makensis zip" @@ -123,6 +123,11 @@ script: | ORIGPATH="$PATH" # Extract the git archive into a dir for each host and build for i in ${HOSTS}; do + case $i in + i?86-* ) bin_name=win32 ;; + x86_64-*) bin_name=win64 ;; + *) bin_name=$i ;; + esac export PATH=${BASEPREFIX}/${i}/native/bin:${ORIGPATH} mkdir -p distsrc-${i} cd distsrc-${i} @@ -134,7 +139,7 @@ script: | make ${MAKEOPTS} make ${MAKEOPTS} -C src check-security make ${MAKEOPTS} -C src check-symbols - make deploy BITCOIN_WIN_INSTALLER="${OUTDIR}/${DISTNAME}-win64-setup-unsigned.exe" + make deploy BITCOIN_WIN_INSTALLER="${OUTDIR}/${DISTNAME}-${bin_name}-setup-unsigned.exe" make install DESTDIR=${INSTALLPATH} cd installed mv ${DISTNAME}/bin/*.dll ${DISTNAME}/lib/ @@ -144,8 +149,8 @@ script: | find ${DISTNAME}/bin -type f -executable -print0 | xargs -0 -n1 -I{} ../contrib/devtools/split-debug.sh {} {} {}.dbg find ${DISTNAME}/lib -type f -print0 | xargs -0 -n1 -I{} ../contrib/devtools/split-debug.sh {} {} {}.dbg cp ../doc/README_windows.txt ${DISTNAME}/readme.txt - find ${DISTNAME} -not -name "*.dbg" -type f | sort | zip -X@ ${OUTDIR}/${DISTNAME}-${i//x86_64-w64-mingw32/win64}.zip - find ${DISTNAME} -name "*.dbg" -type f | sort | zip -X@ ${OUTDIR}/${DISTNAME}-${i//x86_64-w64-mingw32/win64}-debug.zip + find ${DISTNAME} -not -name "*.dbg" -type f | sort | zip -X@ ${OUTDIR}/${DISTNAME}-${bin_name}.zip + find ${DISTNAME} -name "*.dbg" -type f | sort | zip -X@ ${OUTDIR}/${DISTNAME}-${bin_name}-debug.zip cd ../../ rm -rf distsrc-${i} done @@ -153,5 +158,5 @@ script: | cp -rf contrib/windeploy $BUILD_DIR cd $BUILD_DIR/windeploy mkdir unsigned - cp ${OUTDIR}/${DISTNAME}-win64-setup-unsigned.exe unsigned/ + cp ${OUTDIR}/${DISTNAME}-*setup-unsigned.exe unsigned/ find . | sort | tar --mtime="$REFERENCE_DATETIME" --no-recursion --mode='u+rw,go+r-w,a+X' --owner=0 --group=0 -c -T - | gzip -9n > ${OUTDIR}/${DISTNAME}-win-unsigned.tar.gz diff --git a/depends/README.md b/depends/README.md index 5225a6d5c41ef..900c5a384adb6 100644 --- a/depends/README.md +++ b/depends/README.md @@ -24,6 +24,7 @@ Common `host-platform-triplets` for cross compilation are: - `i686-pc-linux-gnu` for Linux 32 bit - `x86_64-pc-linux-gnu` for x86 Linux +- `i686-w64-mingw32` for Win32 - `x86_64-w64-mingw32` for Win64 - `x86_64-apple-darwin16` for macOS - `arm-linux-gnueabihf` for Linux ARM 32 bit diff --git a/share/setup.nsi.in b/share/setup.nsi.in index 5431909bb22d2..c7cd169e4ed52 100644 --- a/share/setup.nsi.in +++ b/share/setup.nsi.in @@ -1,4 +1,4 @@ -Name "@PACKAGE_NAME@ (64-bit)" +Name "@PACKAGE_NAME@ (@WINDOWS_BITS@-bit)" RequestExecutionLevel highest SetCompressor /SOLID lzma @@ -33,7 +33,9 @@ SetDateSave off # Included files !include Sections.nsh !include MUI2.nsh +!if "@WINDOWS_BITS@" == "64" !include x64.nsh +!endif # Variables Var StartMenuGroup @@ -51,7 +53,11 @@ Var StartMenuGroup !insertmacro MUI_LANGUAGE English # Installer attributes +!if "@WINDOWS_BITS@" == "64" InstallDir $PROGRAMFILES64\Bitcoin +!else +InstallDir $PROGRAMFILES\Bitcoin +!endif CRCCheck on XPStyle on BrandingText " " @@ -92,7 +98,7 @@ Section -post SEC0001 !insertmacro MUI_STARTMENU_WRITE_BEGIN Application CreateDirectory $SMPROGRAMS\$StartMenuGroup CreateShortcut "$SMPROGRAMS\$StartMenuGroup\$(^Name).lnk" $INSTDIR\@BITCOIN_GUI_NAME@@EXEEXT@ - CreateShortcut "$SMPROGRAMS\$StartMenuGroup\@PACKAGE_NAME@ (testnet, 64-bit).lnk" "$INSTDIR\@BITCOIN_GUI_NAME@@EXEEXT@" "-testnet" "$INSTDIR\@BITCOIN_GUI_NAME@@EXEEXT@" 1 + CreateShortcut "$SMPROGRAMS\$StartMenuGroup\@PACKAGE_NAME@ (testnet, @WINDOWS_BITS@-bit).lnk" "$INSTDIR\@BITCOIN_GUI_NAME@@EXEEXT@" "-testnet" "$INSTDIR\@BITCOIN_GUI_NAME@@EXEEXT@" 1 CreateShortcut "$SMPROGRAMS\$StartMenuGroup\Uninstall $(^Name).lnk" $INSTDIR\uninstall.exe !insertmacro MUI_STARTMENU_WRITE_END WriteRegStr HKCU "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" DisplayName "$(^Name)" @@ -136,7 +142,7 @@ Section -un.post UNSEC0001 DeleteRegKey HKCU "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" Delete /REBOOTOK "$SMPROGRAMS\$StartMenuGroup\Uninstall $(^Name).lnk" Delete /REBOOTOK "$SMPROGRAMS\$StartMenuGroup\$(^Name).lnk" - Delete /REBOOTOK "$SMPROGRAMS\$StartMenuGroup\@PACKAGE_NAME@ (testnet, 64-bit).lnk" + Delete /REBOOTOK "$SMPROGRAMS\$StartMenuGroup\@PACKAGE_NAME@ (testnet, @WINDOWS_BITS@-bit).lnk" Delete /REBOOTOK "$SMSTARTUP\Bitcoin.lnk" Delete /REBOOTOK $INSTDIR\uninstall.exe Delete /REBOOTOK $INSTDIR\debug.log @@ -158,6 +164,7 @@ SectionEnd # Installer functions Function .onInit InitPluginsDir +!if "@WINDOWS_BITS@" == "64" ${If} ${RunningX64} ; disable registry redirection (enable access to 64-bit portion of registry) SetRegView 64 @@ -165,6 +172,7 @@ Function .onInit MessageBox MB_OK|MB_ICONSTOP "Cannot install 64-bit version on a 32-bit system." Abort ${EndIf} +!endif FunctionEnd # Uninstaller functions diff --git a/src/init.cpp b/src/init.cpp index 794b29b699d20..e5fc4b5a33491 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -931,6 +931,9 @@ bool AppInitBasicSetup(ArgsManager& args) _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); #endif #ifdef WIN32 + // Enable Data Execution Prevention (DEP) + SetProcessDEPPolicy(PROCESS_DEP_ENABLE); + // Enable heap terminate-on-corruption HeapSetInformation(nullptr, HeapEnableTerminationOnCorruption, nullptr, 0); #endif