Skip to content

Commit 4942ac4

Browse files
committed
Merge branch 'hotfix/6.2.7'
2 parents 988df6d + 3ffe381 commit 4942ac4

File tree

12 files changed

+181
-41
lines changed

12 files changed

+181
-41
lines changed

gradle.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
# You should have received a copy of the GNU General Public License
1515
# along with this program. If not, see <http://www.gnu.org/licenses/>.
1616
#
17-
VERSION_NAME=6.2.6
18-
VERSION_CODE=321
17+
VERSION_NAME=6.2.7
18+
VERSION_CODE=322
1919
PACKAGE=it.feio.android.omninotes
2020
MIN_SDK=21
2121
TARGET_SDK=31

omniNotes/build.gradle

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,6 @@ dependencies {
173173
transitive = true
174174
exclude group: "com.android.support"
175175
}
176-
implementation 'com.jakewharton:butterknife:10.2.1'
177-
annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.1'
178176
implementation('org.mnode.ical4j:ical4j:3.0.11') {
179177
exclude group: 'commons.io'
180178
}

omniNotes/src/androidTest/java/it/feio/android/omninotes/testutils/BaseAndroidTestCase.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,9 +188,8 @@ protected static void assertUtilityClassWellDefined(final Class<?> clazz)
188188
}
189189

190190
protected static void assertUtilityClassWellDefined(final Class<?> clazz,
191-
boolean weakClassModifier,
192-
boolean weakConstructorModifier)
193-
throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
191+
boolean weakClassModifier, boolean weakConstructorModifier)
192+
throws NoSuchMethodException, InstantiationException, IllegalAccessException {
194193
if (!weakClassModifier) {
195194
assertTrue("class must be final", Modifier.isFinal(clazz.getModifiers()));
196195
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright (C) 2013-2023 Federico Iosue (federico@iosue.it)
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
package it.feio.android.omninotes.utils
18+
19+
import androidx.test.ext.junit.runners.AndroidJUnit4
20+
import it.feio.android.omninotes.exceptions.checked.ContentSecurityException
21+
import it.feio.android.omninotes.testutils.BaseAndroidTestCase
22+
import it.feio.android.omninotes.utils.Security.Companion.decrypt
23+
import it.feio.android.omninotes.utils.Security.Companion.encrypt
24+
import it.feio.android.omninotes.utils.Security.Companion.validatePath
25+
import org.junit.Assert.assertEquals
26+
import org.junit.Assert.assertNotEquals
27+
import org.junit.Assert.assertThrows
28+
import org.junit.Test
29+
import org.junit.runner.RunWith
30+
31+
@RunWith(AndroidJUnit4::class)
32+
class SecurityTest : BaseAndroidTestCase() {
33+
private val LOREM = ("Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor"
34+
+ " incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco"
35+
+ " laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit "
36+
+ "esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa "
37+
+ "qui officia deserunt mollit anim ID est laborum.")
38+
39+
@Test
40+
@Throws(Exception::class)
41+
fun checkUtilityClassWellDefined() {
42+
assertUtilityClassWellDefined(Security::class.java)
43+
}
44+
45+
@Test
46+
fun encrypt() {
47+
assertNotEquals(TEXT, encrypt(TEXT, PASS))
48+
}
49+
50+
@Test
51+
fun decrypt() {
52+
val encryptedText = encrypt(TEXT, PASS)
53+
54+
assertEquals(TEXT, decrypt(encryptedText, PASS))
55+
assertNotEquals(TEXT, decrypt(encryptedText, "zaza$PASS"))
56+
}
57+
58+
@Test
59+
fun decryptUnencrypted() {
60+
assertNotEquals(0, decrypt(LOREM, PASS)!!.length.toLong())
61+
}
62+
63+
@Test
64+
fun validatePath_startsWithData() {
65+
val path = "file:///data/data/it.feio.android.omninotes.foss/shared_prefs/it.feio.android.omninotes.foss_preferences.xml"
66+
67+
assertThrows(ContentSecurityException::class.java) { validatePath(path) }
68+
}
69+
70+
@Test
71+
fun validatePath_pathTraversal() {
72+
val path = "file:///storage/emulated/0/../../../data/data/it.feio.android.omninotes.foss/shared_prefs/it.feio.android.omninotes.foss_preferences.xml"
73+
74+
assertThrows(ContentSecurityException::class.java) { validatePath(path) }
75+
}
76+
77+
@Test
78+
fun validatePath_valid() {
79+
val path = "/images/screenshot/16844742322307525633366385236595.jpg"
80+
81+
validatePath(path)
82+
}
83+
84+
companion object {
85+
private const val PASS = "12345uselessPasswords"
86+
private const val TEXT = "Today is a - good - day to test useless things!"
87+
}
88+
}

omniNotes/src/main/java/it/feio/android/omninotes/DetailFragment.java

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
import static it.feio.android.omninotes.utils.ConstantsBase.SWIPE_MARGIN;
6262
import static it.feio.android.omninotes.utils.ConstantsBase.SWIPE_OFFSET;
6363
import static it.feio.android.omninotes.utils.ConstantsBase.THUMBNAIL_SIZE;
64+
import static it.feio.android.omninotes.utils.Security.validatePath;
6465
import static java.lang.Integer.parseInt;
6566
import static java.lang.Long.parseLong;
6667

@@ -133,6 +134,7 @@
133134
import it.feio.android.omninotes.async.notes.SaveNoteTask;
134135
import it.feio.android.omninotes.databinding.FragmentDetailBinding;
135136
import it.feio.android.omninotes.db.DbHelper;
137+
import it.feio.android.omninotes.exceptions.checked.ContentSecurityException;
136138
import it.feio.android.omninotes.exceptions.checked.UnhandledIntentException;
137139
import it.feio.android.omninotes.helpers.AttachmentsHelper;
138140
import it.feio.android.omninotes.helpers.IntentHelper;
@@ -167,6 +169,7 @@
167169
import it.feio.android.omninotes.utils.KeyboardUtils;
168170
import it.feio.android.omninotes.utils.PasswordHelper;
169171
import it.feio.android.omninotes.utils.ReminderHelper;
172+
import it.feio.android.omninotes.utils.Security;
170173
import it.feio.android.omninotes.utils.ShortcutHelper;
171174
import it.feio.android.omninotes.utils.StorageHelper;
172175
import it.feio.android.omninotes.utils.TagsHelper;
@@ -551,27 +554,38 @@ private void handleIntents() {
551554
}
552555

553556
private void importAttachments(Intent i) {
554-
555557
if (!i.hasExtra(Intent.EXTRA_STREAM)) {
556558
return;
557559
}
558560

559561
if (i.getExtras().get(Intent.EXTRA_STREAM) instanceof Uri) {
560562
Uri uri = i.getParcelableExtra(Intent.EXTRA_STREAM);
561563
// Google Now passes Intent as text but with audio recording attached the case must be handled like this
562-
if (!INTENT_GOOGLE_NOW.equals(i.getAction())) {
564+
if (validatePath(uri.getPath()) && !INTENT_GOOGLE_NOW.equals(i.getAction())) {
563565
String name = FileHelper.getNameFromUri(mainActivity, uri);
564566
new AttachmentTask(this, uri, name, this).execute();
565567
}
566568
} else {
567569
ArrayList<Uri> uris = i.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
568570
for (Uri uriSingle : uris) {
569-
String name = FileHelper.getNameFromUri(mainActivity, uriSingle);
570-
new AttachmentTask(this, uriSingle, name, this).execute();
571+
if (validatePath(uriSingle.getPath())) {
572+
String name = FileHelper.getNameFromUri(mainActivity, uriSingle);
573+
new AttachmentTask(this, uriSingle, name, this).execute();
574+
}
571575
}
572576
}
573577
}
574578

579+
private boolean validatePath(String path) {
580+
try {
581+
Security.validatePath(path);
582+
return true;
583+
} catch (ContentSecurityException e) {
584+
mainActivity.showMessage(R.string.insecure_content_found, ONStyle.WARN);
585+
return false;
586+
}
587+
}
588+
575589
@SuppressLint("NewApi")
576590
private void initViews() {
577591
// Sets onTouchListener to the whole activity to swipe notes

omniNotes/src/main/java/it/feio/android/omninotes/async/MainMenuTask.java

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,15 @@
2424
import android.content.res.TypedArray;
2525
import android.os.AsyncTask;
2626
import androidx.fragment.app.Fragment;
27-
import butterknife.BindView;
28-
import butterknife.ButterKnife;
2927
import com.pixplicity.easyprefs.library.Prefs;
3028
import de.greenrobot.event.EventBus;
3129
import it.feio.android.omninotes.MainActivity;
3230
import it.feio.android.omninotes.R;
3331
import it.feio.android.omninotes.async.bus.NavigationUpdatedEvent;
32+
import it.feio.android.omninotes.databinding.FragmentNavigationDrawerBinding;
3433
import it.feio.android.omninotes.models.NavigationItem;
3534
import it.feio.android.omninotes.models.adapters.NavDrawerAdapter;
3635
import it.feio.android.omninotes.models.misc.DynamicNavigationLookupTable;
37-
import it.feio.android.omninotes.models.views.NonScrollableListView;
3836
import it.feio.android.omninotes.utils.Navigation;
3937
import java.lang.ref.WeakReference;
4038
import java.util.ArrayList;
@@ -43,18 +41,15 @@
4341

4442
public class MainMenuTask extends AsyncTask<Void, Void, List<NavigationItem>> {
4543

46-
private final WeakReference<Fragment> mFragmentWeakReference;
44+
private final WeakReference<Fragment> fragmentWeakReference;
4745
private final MainActivity mainActivity;
48-
@BindView(R.id.drawer_nav_list)
49-
NonScrollableListView mDrawerList;
50-
@BindView(R.id.drawer_tag_list)
51-
NonScrollableListView mDrawerCategoriesList;
5246

47+
FragmentNavigationDrawerBinding navDrawer;
5348

54-
public MainMenuTask(Fragment mFragment) {
55-
mFragmentWeakReference = new WeakReference<>(mFragment);
56-
this.mainActivity = (MainActivity) mFragment.getActivity();
57-
ButterKnife.bind(this, mFragment.getView());
49+
public MainMenuTask(Fragment fragment) {
50+
fragmentWeakReference = new WeakReference<>(fragment);
51+
mainActivity = (MainActivity) fragment.getActivity();
52+
navDrawer = FragmentNavigationDrawerBinding.inflate(fragment.getLayoutInflater());
5853
}
5954

6055
@Override
@@ -65,33 +60,31 @@ protected List<NavigationItem> doInBackground(Void... params) {
6560
@Override
6661
protected void onPostExecute(final List<NavigationItem> items) {
6762
if (isAlive()) {
68-
mDrawerList.setAdapter(new NavDrawerAdapter(mainActivity, items));
69-
mDrawerList.setOnItemClickListener((arg0, arg1, position, arg3) -> {
70-
String navigation = mFragmentWeakReference.get().getResources().getStringArray(R.array
63+
navDrawer.drawerNavList.setAdapter(new NavDrawerAdapter(mainActivity, items));
64+
navDrawer.drawerNavList.setOnItemClickListener((arg0, arg1, position, arg3) -> {
65+
String navigation = fragmentWeakReference.get().getResources().getStringArray(R.array
7166
.navigation_list_codes)[items.get(position).getArrayIndex()];
7267
updateNavigation(position, navigation);
7368
});
74-
mDrawerList.justifyListViewHeightBasedOnChildren();
69+
navDrawer.drawerNavList.justifyListViewHeightBasedOnChildren();
7570
}
7671
}
7772

7873
private void updateNavigation(int position, String navigation) {
7974
if (mainActivity.updateNavigation(navigation)) {
80-
mDrawerList.setItemChecked(position, true);
81-
if (mDrawerCategoriesList != null) {
82-
mDrawerCategoriesList.setItemChecked(0, false); // Called to force redraw
83-
}
75+
navDrawer.drawerNavList.setItemChecked(position, true);
76+
navDrawer.drawerTagList.setItemChecked(0, false); // Called to force redraw
8477
mainActivity.getIntent().setAction(Intent.ACTION_MAIN);
8578
EventBus.getDefault()
86-
.post(new NavigationUpdatedEvent(mDrawerList.getItemAtPosition(position)));
79+
.post(new NavigationUpdatedEvent(navDrawer.drawerNavList.getItemAtPosition(position)));
8780
}
8881
}
8982

9083
private boolean isAlive() {
91-
return mFragmentWeakReference.get() != null
92-
&& mFragmentWeakReference.get().isAdded()
93-
&& mFragmentWeakReference.get().getActivity() != null
94-
&& !mFragmentWeakReference.get().getActivity().isFinishing();
84+
return fragmentWeakReference.get() != null
85+
&& fragmentWeakReference.get().isAdded()
86+
&& fragmentWeakReference.get().getActivity() != null
87+
&& !fragmentWeakReference.get().getActivity().isFinishing();
9588
}
9689

9790
private List<NavigationItem> buildMainMenu() {

omniNotes/src/main/java/it/feio/android/omninotes/db/DbHelper.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,7 +1046,6 @@ public Stats getStats() {
10461046
mStats.setCharsAvg(avgChars);
10471047

10481048
// Everything about attachments
1049-
int attachmentsAll = 0;
10501049
int images = 0;
10511050
int videos = 0;
10521051
int audioRecordings = 0;
@@ -1067,7 +1066,7 @@ public Stats getStats() {
10671066
files++;
10681067
}
10691068
}
1070-
mStats.setAttachments(attachmentsAll);
1069+
mStats.setAttachments(attachments.size());
10711070
mStats.setImages(images);
10721071
mStats.setVideos(videos);
10731072
mStats.setAudioRecordings(audioRecordings);
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright (C) 2013-2023 Federico Iosue (federico@iosue.it)
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
package it.feio.android.omninotes.exceptions.checked
19+
20+
class ContentSecurityException(message: String) : SecurityException()

omniNotes/src/main/java/it/feio/android/omninotes/utils/Security.kt

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,20 @@
1717

1818
package it.feio.android.omninotes.utils
1919

20+
import android.net.Uri
2021
import android.util.Base64
22+
import it.feio.android.omninotes.exceptions.checked.ContentSecurityException
2123
import it.feio.android.omninotes.helpers.LogDelegate
2224
import java.nio.charset.StandardCharsets
2325
import java.security.MessageDigest
2426
import java.security.NoSuchAlgorithmException
25-
import javax.crypto.*
27+
import javax.crypto.Cipher
28+
import javax.crypto.SecretKeyFactory
2629
import javax.crypto.spec.DESKeySpec
30+
import kotlin.jvm.Throws
2731

28-
class Security private constructor(){
32+
33+
class Security private constructor() {
2934

3035
companion object {
3136
@JvmStatic
@@ -82,5 +87,14 @@ class Security private constructor(){
8287
}
8388
}
8489

90+
@JvmStatic
91+
@Throws(ContentSecurityException::class)
92+
fun validatePath(path: String?) {
93+
val uri = Uri.parse(path).path
94+
if (uri?.startsWith("/data")!! || uri.contains("../")) {
95+
throw ContentSecurityException("Invalid")
96+
}
97+
}
98+
8599
}
86100
}

omniNotes/src/main/res/layout/activity_stats.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@
271271
android:gravity="end"
272272
android:paddingEnd="7dp"
273273
android:paddingStart="7dp"
274-
android:text="@string/stats_attachments"
274+
android:text="@string/stats_images"
275275
android:textStyle="bold"></TextView>
276276

277277
<TextView

omniNotes/src/main/res/raw/changelog.xml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,20 @@
1616
-->
1717
<changelog bulletedList="true">
1818

19+
<changelogversion
20+
changeDate="May 31, 2023"
21+
versionName="6.2.7">
22+
<changelogtext>[u]Fix[/u] Security path traversal vulnerability</changelogtext>
23+
<changelogtext>[u]Fix[/u] Attachments' count in statistics (thanks to S.Vago)</changelogtext>
24+
</changelogversion>
25+
26+
<changelogversion
27+
changeDate="Apr 30, 2023"
28+
versionName="6.2.6">
29+
<changelogtext>[i]Improved![/i] Reduced required permissions and removed some legacy code</changelogtext>
30+
<changelogtext>[u]Fix[/u] Minor backup related fixes</changelogtext>
31+
</changelogversion>
32+
1933
<changelogversion
2034
changeDate="Apr 20, 2023"
2135
versionName="6.2.5">

omniNotes/src/main/res/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@
231231
<string name="undo_changes_note_confirmation">Confirm undoing changes?</string>
232232
<string name="done">Done</string>
233233
<string name="nothing_selected">Nothing selected</string>
234+
<string name="insecure_content_found">Some of the received content was dangerous, it has been ignored</string>
234235

235236
<!-- Settings -->
236237
<string name="dashclock_description">Displays some statistics and expiring reminders from Omni Notes</string>

0 commit comments

Comments
 (0)