aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSven Gothel <[email protected]>2022-07-07 14:39:40 +0200
committerSven Gothel <[email protected]>2022-07-07 14:39:40 +0200
commit2ebef56ba148c88c6cb1d864b4773df574bb4168 (patch)
treeb843f8f84bee0211ff4d4c1430873a7f36e72139
parent014c5f1b0f49f5be2595ef74511a5e09c157f704 (diff)
Migrate `void zeroByteBuffer(final ByteBuffer buf)` from Cipherpack's CPUtils -> MemUtil, incl. unit test
-rw-r--r--java_jni/jni/CMakeLists.txt1
-rw-r--r--java_jni/jni/jau/jau_io_MemUtil.cxx155
-rw-r--r--java_jni/org/jau/io/MemUtil.java48
-rw-r--r--test/java/jau/test/io/TestMemBuffers00.java142
4 files changed, 346 insertions, 0 deletions
diff --git a/java_jni/jni/CMakeLists.txt b/java_jni/jni/CMakeLists.txt
index 77236ca..97bd124 100644
--- a/java_jni/jni/CMakeLists.txt
+++ b/java_jni/jni/CMakeLists.txt
@@ -25,6 +25,7 @@ set (jaulib_jni_JNI_SRCS
${PROJECT_SOURCE_DIR}/java_jni/jni/jau/JVM_JNI8.cxx
${PROJECT_SOURCE_DIR}/java_jni/jni/jau/MachineDataInfoRuntime.cxx
${PROJECT_SOURCE_DIR}/java_jni/jni/jau/jau_fs_FileUtil.cxx
+ ${PROJECT_SOURCE_DIR}/java_jni/jni/jau/jau_io_MemUtil.cxx
${PROJECT_SOURCE_DIR}/java_jni/jni/jau/jau_io_UriTk.cxx
${PROJECT_SOURCE_DIR}/java_jni/jni/jau/jau_sys_Clock.cxx
${PROJECT_SOURCE_DIR}/java_jni/jni/jau/ByteInStream_File.cxx
diff --git a/java_jni/jni/jau/jau_io_MemUtil.cxx b/java_jni/jni/jau/jau_io_MemUtil.cxx
new file mode 100644
index 0000000..9472167
--- /dev/null
+++ b/java_jni/jni/jau/jau_io_MemUtil.cxx
@@ -0,0 +1,155 @@
+/*
+ * Author: Sven Gothel <[email protected]>
+ * Copyright (c) 2022 Gothel Software e.K.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "org_jau_io_MemUtil.h"
+
+#include <cstdint>
+#include <cinttypes>
+
+#include <time.h>
+
+#include <jau/environment.hpp>
+
+#include "jau/jni/helper_jni.hpp"
+
+void Java_org_jau_io_MemUtil_zeroByteBuffer(JNIEnv *env, jclass clazz, jobject jbuf) {
+ (void)clazz;
+ if( nullptr != jbuf ) {
+ void* address = env->GetDirectBufferAddress(jbuf);
+ jlong capacity = env->GetDirectBufferCapacity(jbuf);
+ if( nullptr != address && 0 < capacity ) {
+ ::explicit_bzero(address, capacity);
+ }
+ }
+}
+
+#if 0
+
+jboolean Java_org_jau_io_MemUtil_zeroString(JNIEnv *env, jclass clazz, jstring jstr) {
+ (void)clazz;
+ if( nullptr != jstr ) {
+ size_t len = env->GetStringLength(jstr);
+ if( 0 == len ) {
+ return JNI_TRUE;
+ }
+ jboolean is_copy = JNI_FALSE;
+ const jchar* str_u16 = env->GetStringCritical(jstr, &is_copy);
+ DBG_PRINT("zeroString.0: jstr %p, copy %d", str_u16, is_copy);
+ if( nullptr != str_u16 ) {
+ ::explicit_bzero((void*)str_u16, len * sizeof(jchar));
+ env->ReleaseStringCritical(jstr, str_u16);
+ } else {
+ ERR_PRINT("GetStringCritical() return null");
+ }
+ if( nullptr == str_u16 || JNI_TRUE == is_copy ) {
+ // try harder ..
+ jclass string_clazz = jau::jni::search_class(env, "java/lang/String");
+ jfieldID f_value = jau::jni::search_field(env, string_clazz, "value", "[B", false /* is_static */);
+ jbyteArray jstr_value = (jbyteArray)env->GetObjectField(jstr, f_value);
+ jau::jni::java_exception_check_and_throw(env, E_FILE_LINE);
+
+ if( nullptr == jstr_value ) {
+ ERR_PRINT("GetObjectField(value) is null");
+ return JNI_FALSE;
+ }
+ const size_t jstr_value_size = env->GetArrayLength(jstr_value);
+ if( 0 == jstr_value_size ) {
+ ERR_PRINT("GetArrayLength(address byte array) is null");
+ return JNI_FALSE;
+ }
+ jau::jni::JNICriticalArray<uint8_t, jbyteArray> criticalArray(env); // RAII - release
+ uint8_t * ptr = criticalArray.get(jstr_value, criticalArray.Mode::UPDATE_AND_RELEASE);
+ DBG_PRINT("zeroString.1: value: %p, len %zu, is_copy %d", ptr, jstr_value_size, criticalArray.getIsCopy());
+ if( NULL == ptr ) {
+ ERR_PRINT("GetPrimitiveArrayCritical(address byte array) is null");
+ return JNI_FALSE;
+ }
+ ::explicit_bzero((void*)ptr, jstr_value_size);
+ }
+ }
+ return JNI_TRUE;
+}
+
+#include <locale>
+#include <codecvt>
+
+// utility wrapper to adapt locale-bound facets for wstring/wbuffer convert
+template<class Facet>
+struct deletable_facet : Facet
+{
+ template<class ...Args>
+ deletable_facet(Args&& ...args) : Facet(std::forward<Args>(args)...) {}
+ ~deletable_facet() {}
+};
+
+jobject Java_org_jau_io_MemUtil_toByteBufferImpl(JNIEnv *env, jclass clazz, jstring jstr) {
+ (void)clazz;
+ if( nullptr == jstr ) {
+ return nullptr;
+ }
+ size_t jstr_len = env->GetStringLength(jstr);
+ if( 0 == jstr_len ) {
+ return nullptr;
+ }
+ jboolean isCopy = JNI_TRUE;
+ const jchar* jstr_u16 = env->GetStringCritical(jstr, &isCopy);
+ if( nullptr == jstr_u16 ) {
+ return nullptr;
+ }
+ std::u16string str_u16(jstr_u16, jstr_u16+jstr_len);
+ if( JNI_TRUE == isCopy ) {
+ ::explicit_bzero((void*)jstr_u16, jstr_len * sizeof(jchar));
+ }
+ env->ReleaseStringCritical(jstr, jstr_u16);
+
+ // UTF-16/char16_t to UTF-8
+ std::wstring_convert<deletable_facet<std::codecvt<char16_t, char, std::mbstate_t>>, char16_t> conv16;
+ // std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> conv16;
+ std::string u8_conv = conv16.to_bytes(str_u16);
+ ::explicit_bzero((void*)str_u16.data(), str_u16.size() * sizeof(char16_t));
+ const size_t u8_size = u8_conv.size();
+
+ jmethodID buffer_new = jau::jni::search_method(env, clazz, "newDirectByteBuffer", "(I)Ljava/nio/ByteBuffer;", true);
+ jobject jdest = env->CallStaticObjectMethod(clazz, buffer_new, (jint)u8_size);
+ jau::jni::java_exception_check_and_throw(env, E_FILE_LINE);
+ if( nullptr == jdest ) {
+ ERR_PRINT("Couldn't allocated ByteBuffer w/ capacity %zu", u8_size);
+ return nullptr;
+ }
+ char* jdest_address = (char*)env->GetDirectBufferAddress(jdest);
+ size_t jdest_capacity = (size_t)env->GetDirectBufferCapacity(jdest);
+ if( nullptr == jdest_address || 0 == jdest_capacity ) {
+ ERR_PRINT("ByteBuffer w/ capacity %zu has zero address (%p) or length (%zu)", u8_size, jdest_address, jdest_capacity);
+ return nullptr;
+ }
+ if( u8_size > jdest_capacity ) {
+ ERR_PRINT("ByteBuffer w/ capacity %zu < required size %zu)", jdest_capacity, u8_size);
+ return nullptr;
+ }
+ ::memcpy(jdest_address, u8_conv.data(), u8_size);
+ ::explicit_bzero((void*)u8_conv.data(), u8_size);
+ return jdest;
+}
+
+#endif
diff --git a/java_jni/org/jau/io/MemUtil.java b/java_jni/org/jau/io/MemUtil.java
new file mode 100644
index 0000000..ea3cbb8
--- /dev/null
+++ b/java_jni/org/jau/io/MemUtil.java
@@ -0,0 +1,48 @@
+package org.jau.io;
+
+import java.nio.ByteBuffer;
+
+public class MemUtil {
+ /**
+ * Zeros all bytes of given direct NIO byte buffer.
+ */
+ public static native void zeroByteBuffer(final ByteBuffer buf);
+
+ /**
+ * Zeros all underlying bytes of given Java string.
+ *
+ * Implementation either uses the JNI GetStringCritical() with is_copy == false or the String backing array `value`.
+ *
+ * @return true if successful, i.e. the underlying bytes could have been zeroed, otherwise returns false w/o zero'ed content.
+ public static native boolean zeroString(final String str);
+ */
+
+ /**
+ * Converts the Java string to a native direct ByteBuffer as UTF-8
+ * allowing better control over its memory region to {@link #zeroByteBuffer(ByteBuffer) zero} it after usage.
+ *
+ * @param str the string to convert
+ * @param zerostr pass true to zero the bytes of the given Java string afterwards (recommended!)
+ * @return direct ByteBuffer instance suitable for Cipherpack.
+ public static ByteBuffer to_ByteBuffer(final String str, final boolean zerostr) {
+ final ByteBuffer res = toByteBufferImpl(str);
+ res.limit(res.capacity());
+ res.position(0);
+ if( zerostr ) {
+ zeroString(str);
+ }
+ return res;
+ }
+ private static native ByteBuffer toByteBufferImpl(final String str);
+
+ public static String to_String(final ByteBuffer bb, final Charset cs) {
+ final int p0 = bb.position();
+ bb.position(0);
+ final byte[] bb_bytes = new byte[bb.limit()];
+ bb.get(bb_bytes);
+ bb.position(p0);
+ return new String(bb_bytes, cs);
+ }
+ */
+
+}
diff --git a/test/java/jau/test/io/TestMemBuffers00.java b/test/java/jau/test/io/TestMemBuffers00.java
new file mode 100644
index 0000000..780f347
--- /dev/null
+++ b/test/java/jau/test/io/TestMemBuffers00.java
@@ -0,0 +1,142 @@
+/**
+ * Author: Sven Gothel <[email protected]>
+ * Copyright (c) 2022 Gothel Software e.K.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package jau.test.io;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import org.jau.io.Buffers;
+import org.jau.io.MemUtil;
+import org.junit.Assert;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+import jau.pkg.PlatformRuntime;
+import jau.test.junit.util.JunitTracer;
+
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class TestMemBuffers00 extends JunitTracer {
+ static final boolean DEBUG = false;
+
+ @Test(timeout = 10000)
+ public final void test01_ByteBuffer() {
+ PlatformRuntime.checkInitialized();
+ final int len = 10;
+
+ final ByteBuffer b1 = Buffers.newDirectByteBuffer(len * 2);
+ Assert.assertEquals(len * 2, b1.capacity());
+ Assert.assertEquals(len * 2, b1.limit());
+ Assert.assertEquals(0, b1.position());
+ Assert.assertEquals(len * 2, b1.remaining());
+ Assert.assertEquals(ByteOrder.nativeOrder(), b1.order());
+
+ for(int i=0; i<len; ++i) {
+ b1.put((byte)0xaa);
+ }
+ Assert.assertEquals(len, b1.position());
+ b1.flip();
+ Assert.assertEquals(len, b1.limit());
+ Assert.assertEquals(0, b1.position());
+ Assert.assertEquals(len, b1.remaining());
+
+ Assert.assertEquals((byte)0xaa, b1.get(0));
+ Assert.assertEquals((byte)0xaa, b1.get(len-1));
+ MemUtil.zeroByteBuffer(b1);
+ Assert.assertEquals((byte)0x00, b1.get(0));
+ Assert.assertEquals((byte)0x00, b1.get(len-1));
+ }
+
+/**
+ @Test(timeout = 10000)
+ public final void test02_String_ByteBuffer() {
+ PlatformRuntime.checkInitialized();
+ final ByteBuffer b0;
+ {
+ final String s1 = "0123456789"; // len = 10
+ b0 = CPUtils.to_ByteBuffer(s1, false);
+ final String s2 = new String(s1.getBytes()); // true copy before deletion
+ final boolean r2 = CPUtils.zeroString(s2);
+ // Assert.assertTrue( r1 );
+ CPUtils.fprintf_td(System.err, "test01_enc_dec_file_ok.1a: s1: '%s', len %d\n", s1, s1.length());
+ CPUtils.fprintf_td(System.err, "test01_enc_dec_file_ok.1a: s2: '%s', len %d, zeroString result %b\n", s2, s2.length(), r2);
+
+ final boolean r1 = CPUtils.zeroString(s1);
+ CPUtils.fprintf_td(System.err, "test01_enc_dec_file_ok.1b: s1: '%s', len %d, zeroString result %b\n", s1, s1.length(), r1);
+ }
+ {
+ // Ooops, the internalized string at compile time got zeroed out
+ final String s1 = "0123456789"; // len = 10
+ CPUtils.fprintf_td(System.err, "test01_enc_dec_file_ok.1c: s1: '%s', len %d\n", s1, s1.length());
+ }
+ {
+ // That works, using dynamic storage
+ final String s1 = CPUtils.to_String(b0, StandardCharsets.UTF_8);
+ CPUtils.fprintf_td(System.err, "test01_enc_dec_file_ok.1d: s1: '%s', len %d\n", s1, s1.length());
+ }
+ {
+ final String s1 = CPUtils.to_String(b0, StandardCharsets.UTF_8);
+ CPUtils.fprintf_td(System.err, "test01_enc_dec_file_ok.2a: s1: '%s', len %d\n", s1, s1.length());
+
+ final ByteBuffer b1 = CPUtils.newDirectByteBuffer(s1.length() * 2);
+ Assert.assertEquals(s1.length() * 2, b1.capacity());
+ Assert.assertEquals(s1.length() * 2, b1.limit());
+ Assert.assertEquals(0, b1.position());
+ Assert.assertEquals(s1.length() * 2, b1.remaining());
+
+ b1.put(s1.getBytes(StandardCharsets.UTF_8));
+ Assert.assertEquals(s1.length(), b1.position());
+ b1.flip();
+ Assert.assertEquals(s1.length(), b1.limit());
+ Assert.assertEquals(0, b1.position());
+ Assert.assertEquals(s1.length(), b1.remaining());
+ {
+ final String b1_str = CPUtils.to_String(b1, StandardCharsets.UTF_8);
+ CPUtils.fprintf_td(System.err, "test01_enc_dec_file_ok.2b: b1: '%s', len %d/%d\n", b1_str, b1_str.length(), b1.limit());
+ }
+ Assert.assertEquals(s1.length(), b1.limit());
+ Assert.assertEquals(0, b1.position());
+ Assert.assertEquals(s1.length(), b1.remaining());
+
+ final String s2 = new String(s1.getBytes()); // true copy
+ final ByteBuffer b2 = CPUtils.to_ByteBuffer(s2, true);
+ Assert.assertEquals(s1.length(), b2.capacity());
+ Assert.assertEquals(s1.length(), b2.limit());
+ Assert.assertEquals(0, b2.position());
+ Assert.assertEquals(s1.length(), b2.remaining());
+ {
+ final byte[] b2_bytes = new byte[b2.remaining()];
+ b2.get(b2_bytes);
+ b2.rewind(); // yada yada yada (relative get, absolute @ JDK13
+ final String b2_str = new String(b2_bytes, StandardCharsets.UTF_8);
+ CPUtils.fprintf_td(System.err, "test01_enc_dec_file_ok.2c: b2: '%s', len %d/%d\n", b2_str, b2_str.length(), b2.limit());
+ }
+ }
+ }
+*/
+ public static void main(final String args[]) {
+ org.junit.runner.JUnitCore.main(TestMemBuffers00.class.getName());
+ }
+}