Skip to content

Commit 14ae7dd

Browse files
abdulraqeeb33AR Abdul Azeezcursoragentfadi-george
authored
fix: [SDK-5335] validate public API inputs to prevent native crashes (#901)
Co-authored-by: AR Abdul Azeez <abdul@onesignal.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Fadi George <fadii925@gmail.com>
1 parent 3bb2663 commit 14ae7dd

12 files changed

Lines changed: 415 additions & 47 deletions

File tree

‎com.onesignal.unity.android/Runtime/AndroidDebugManager.cs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ public LogLevel LogLevel
4848
get => _logLevel;
4949
set
5050
{
51+
if (InputGuard.IsInvalidLogLevel(value, "setLogLevel"))
52+
return;
5153
_logLevel = value;
5254
_debug.Call("setLogLevel", ToLogLevel(value));
5355
}
@@ -58,6 +60,8 @@ public LogLevel AlertLevel
5860
get => _alertLevel;
5961
set
6062
{
63+
if (InputGuard.IsInvalidLogLevel(value, "setAlertLevel"))
64+
return;
6165
_alertLevel = value;
6266
_debug.Call("setAlertLevel", ToLogLevel(value));
6367
}

‎com.onesignal.unity.android/Runtime/AndroidInAppMessagesManager.cs‎

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
using System;
2929
using System.Collections.Generic;
30+
using OneSignalSDK;
3031
using OneSignalSDK.Android.Utilities;
3132
using OneSignalSDK.InAppMessages;
3233
using OneSignalSDK.InAppMessages.Internal;
@@ -56,16 +57,36 @@ public bool Paused
5657
set => _inAppMessages.Call("setPaused", value);
5758
}
5859

59-
public void AddTrigger(string key, string value) =>
60+
public void AddTrigger(string key, string value)
61+
{
62+
if (
63+
InputGuard.IsMissing(key, "addTrigger: key")
64+
|| InputGuard.IsNull(value, "addTrigger: value")
65+
)
66+
return;
6067
_inAppMessages.Call("addTrigger", key, value);
68+
}
6169

62-
public void AddTriggers(Dictionary<string, string> triggers) =>
70+
public void AddTriggers(Dictionary<string, string> triggers)
71+
{
72+
if (InputGuard.HasMissingEntries(triggers, "addTriggers", true))
73+
return;
6374
_inAppMessages.Call("addTriggers", triggers.ToMap());
75+
}
6476

65-
public void RemoveTrigger(string key) => _inAppMessages.Call("removeTrigger", key);
77+
public void RemoveTrigger(string key)
78+
{
79+
if (InputGuard.IsMissing(key, "removeTrigger: key"))
80+
return;
81+
_inAppMessages.Call("removeTrigger", key);
82+
}
6683

67-
public void RemoveTriggers(params string[] keys) =>
84+
public void RemoveTriggers(params string[] keys)
85+
{
86+
if (InputGuard.IsMissingAny(keys, "removeTriggers: key"))
87+
return;
6888
_inAppMessages.Call("removeTriggers", keys.ToArrayList());
89+
}
6990

7091
public void ClearTriggers() => _inAppMessages.Call("clearTriggers");
7192

‎com.onesignal.unity.android/Runtime/AndroidSessionManager.cs‎

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,28 @@ public AndroidSessionManager(AndroidJavaClass sdkClass)
3939
_session = sdkClass.CallStatic<AndroidJavaObject>("getSession");
4040
}
4141

42-
public void AddOutcome(string name) => _session.Call("addOutcome", name);
42+
public void AddOutcome(string name)
43+
{
44+
if (InputGuard.IsMissing(name, "addOutcome: name"))
45+
return;
46+
_session.Call("addOutcome", name);
47+
}
4348

44-
public void AddUniqueOutcome(string name) => _session.Call("addUniqueOutcome", name);
49+
public void AddUniqueOutcome(string name)
50+
{
51+
if (InputGuard.IsMissing(name, "addUniqueOutcome: name"))
52+
return;
53+
_session.Call("addUniqueOutcome", name);
54+
}
4555

46-
public void AddOutcomeWithValue(string name, float value) =>
56+
public void AddOutcomeWithValue(string name, float value)
57+
{
58+
if (
59+
InputGuard.IsMissing(name, "addOutcomeWithValue: name")
60+
|| InputGuard.IsNotFinite(value, "addOutcomeWithValue: value")
61+
)
62+
return;
4763
_session.Call("addOutcomeWithValue", name, value);
64+
}
4865
}
4966
}

‎com.onesignal.unity.android/Runtime/AndroidUserManager.cs‎

Lines changed: 89 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
using System;
2929
using System.Collections.Generic;
30+
using OneSignalSDK;
3031
using OneSignalSDK.Android.User.Models;
3132
using OneSignalSDK.Android.Utilities;
3233
using OneSignalSDK.User;
@@ -73,7 +74,13 @@ public IPushSubscription PushSubscription
7374

7475
public string Language
7576
{
76-
set => _user.Call("setLanguage", value);
77+
set
78+
{
79+
// Empty string is the reset to the device language. Null is not.
80+
if (InputGuard.IsNull(value, "setLanguage: language"))
81+
return;
82+
_user.Call("setLanguage", value);
83+
}
7784
}
7885

7986
public Dictionary<string, string> GetTags()
@@ -82,35 +89,102 @@ public Dictionary<string, string> GetTags()
8289
return obj.MapToDictionary();
8390
}
8491

85-
public void AddTag(string key, string value) => _user.Call("addTag", key, value);
92+
public void AddTag(string key, string value)
93+
{
94+
if (
95+
InputGuard.IsMissing(key, "addTag: key")
96+
|| InputGuard.IsNull(value, "addTag: value")
97+
)
98+
return;
99+
_user.Call("addTag", key, value);
100+
}
86101

87-
public void AddTags(Dictionary<string, string> tags) => _user.Call("addTags", tags.ToMap());
102+
public void AddTags(Dictionary<string, string> tags)
103+
{
104+
if (InputGuard.HasMissingEntries(tags, "addTags", true))
105+
return;
106+
_user.Call("addTags", tags.ToMap());
107+
}
88108

89-
public void RemoveTag(string key) => _user.Call("removeTag", key);
109+
public void RemoveTag(string key)
110+
{
111+
if (InputGuard.IsMissing(key, "removeTag: key"))
112+
return;
113+
_user.Call("removeTag", key);
114+
}
90115

91-
public void RemoveTags(params string[] keys) =>
116+
public void RemoveTags(params string[] keys)
117+
{
118+
if (InputGuard.IsMissingAny(keys, "removeTags: key"))
119+
return;
92120
_user.Call("removeTags", keys.ToArrayList());
121+
}
93122

94-
public void AddAlias(string label, string id) => _user.Call("addAlias", label, id);
123+
public void AddAlias(string label, string id)
124+
{
125+
if (
126+
InputGuard.IsMissing(label, "addAlias: label")
127+
|| InputGuard.IsMissing(id, "addAlias: id")
128+
)
129+
return;
130+
_user.Call("addAlias", label, id);
131+
}
95132

96-
public void AddAliases(Dictionary<string, string> aliases) =>
133+
public void AddAliases(Dictionary<string, string> aliases)
134+
{
135+
if (InputGuard.HasMissingEntries(aliases, "addAliases", false))
136+
return;
97137
_user.Call("addAliases", aliases.ToMap());
138+
}
98139

99-
public void RemoveAlias(string label) => _user.Call("removeAlias", label);
140+
public void RemoveAlias(string label)
141+
{
142+
if (InputGuard.IsMissing(label, "removeAlias: label"))
143+
return;
144+
_user.Call("removeAlias", label);
145+
}
100146

101-
public void RemoveAliases(params string[] labels) =>
147+
public void RemoveAliases(params string[] labels)
148+
{
149+
if (InputGuard.IsMissingAny(labels, "removeAliases: label"))
150+
return;
102151
_user.Call("removeAliases", labels.ToArrayList());
152+
}
103153

104-
public void AddEmail(string email) => _user.Call("addEmail", email);
154+
public void AddEmail(string email)
155+
{
156+
if (InputGuard.IsMissing(email, "addEmail: email"))
157+
return;
158+
_user.Call("addEmail", email);
159+
}
105160

106-
public void RemoveEmail(string email) => _user.Call("removeEmail", email);
161+
public void RemoveEmail(string email)
162+
{
163+
if (InputGuard.IsMissing(email, "removeEmail: email"))
164+
return;
165+
_user.Call("removeEmail", email);
166+
}
107167

108-
public void AddSms(string sms) => _user.Call("addSms", sms);
168+
public void AddSms(string sms)
169+
{
170+
if (InputGuard.IsMissing(sms, "addSms: sms"))
171+
return;
172+
_user.Call("addSms", sms);
173+
}
109174

110-
public void RemoveSms(string sms) => _user.Call("removeSms", sms);
175+
public void RemoveSms(string sms)
176+
{
177+
if (InputGuard.IsMissing(sms, "removeSms: sms"))
178+
return;
179+
_user.Call("removeSms", sms);
180+
}
111181

112-
public void TrackEvent(string name, Dictionary<string, object> properties = null) =>
113-
_user.Call("trackEvent", name, properties?.ToMap());
182+
public void TrackEvent(string name, Dictionary<string, object> properties = null)
183+
{
184+
if (InputGuard.IsMissing(name, "trackEvent: name"))
185+
return;
186+
_user.Call("trackEvent", name, InputGuard.ReplaceNonFiniteNumbers(properties)?.ToMap());
187+
}
114188

115189
public void Initialize()
116190
{

‎com.onesignal.unity.android/Runtime/OneSignalAndroid.cs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ public override bool ConsentRequired
130130

131131
public override void Initialize(string appId)
132132
{
133+
if (InputGuard.IsMissing(appId, "initialize: appId"))
134+
return;
133135
var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
134136
var activity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
135137

@@ -199,6 +201,8 @@ private static void _enableHardwareAcceleration(AndroidJavaObject activity)
199201

200202
public override void Login(string externalId, string jwtBearerToken = null)
201203
{
204+
if (InputGuard.IsMissing(externalId, "login: externalId"))
205+
return;
202206
_sdkClass.CallStatic("login", externalId, jwtBearerToken);
203207
}
204208

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using OneSignalSDK.Debug.Models;
4+
using OneSignalSDK.Debug.Utilities;
5+
6+
namespace OneSignalSDK
7+
{
8+
internal static class InputGuard
9+
{
10+
public static bool IsMissing(string value, string api)
11+
{
12+
if (!string.IsNullOrEmpty(value))
13+
return false;
14+
SDKDebug.Error(api + " is required");
15+
return true;
16+
}
17+
18+
public static bool IsMissingAny(string[] values, string api)
19+
{
20+
if (values == null)
21+
return IsMissing(null, api);
22+
foreach (var value in values)
23+
{
24+
if (IsMissing(value, api))
25+
return true;
26+
}
27+
return false;
28+
}
29+
30+
public static bool IsNull(object value, string api)
31+
{
32+
if (value != null)
33+
return false;
34+
SDKDebug.Error(api + " is required");
35+
return true;
36+
}
37+
38+
public static bool IsNotFinite(float value, string api)
39+
{
40+
if (!float.IsNaN(value) && !float.IsInfinity(value))
41+
return false;
42+
SDKDebug.Error(api + " must be a finite number");
43+
return true;
44+
}
45+
46+
public static bool IsInvalidLogLevel(LogLevel value, string api)
47+
{
48+
if (Enum.IsDefined(typeof(LogLevel), value))
49+
return false;
50+
SDKDebug.Error(api + " is not a valid log level");
51+
return true;
52+
}
53+
54+
// Native JSON serializers reject NaN and Infinity (Android throws, iOS drops the payload).
55+
public static Dictionary<string, object> ReplaceNonFiniteNumbers(
56+
IDictionary<string, object> values
57+
)
58+
{
59+
if (values == null)
60+
return null;
61+
var result = new Dictionary<string, object>(values.Count);
62+
foreach (var pair in values)
63+
result[pair.Key] = ReplaceNonFiniteNumber(pair.Value);
64+
return result;
65+
}
66+
67+
private static object ReplaceNonFiniteNumber(object value)
68+
{
69+
switch (value)
70+
{
71+
case double d when double.IsNaN(d) || double.IsInfinity(d):
72+
case float f when float.IsNaN(f) || float.IsInfinity(f):
73+
return null;
74+
case IDictionary<string, object> dict:
75+
return ReplaceNonFiniteNumbers(dict);
76+
case IList<object> list:
77+
var items = new List<object>(list.Count);
78+
foreach (var item in list)
79+
items.Add(ReplaceNonFiniteNumber(item));
80+
return items;
81+
default:
82+
return value;
83+
}
84+
}
85+
86+
public static bool HasMissingEntries(
87+
IDictionary<string, string> values,
88+
string api,
89+
bool allowEmptyValue
90+
)
91+
{
92+
if (values == null)
93+
return IsNull(values, api);
94+
foreach (var pair in values)
95+
{
96+
if (IsMissing(pair.Key, api + ": key"))
97+
return true;
98+
if (
99+
allowEmptyValue
100+
? IsNull(pair.Value, api + ": value")
101+
: IsMissing(pair.Value, api + ": value")
102+
)
103+
return true;
104+
}
105+
return false;
106+
}
107+
}
108+
}

‎com.onesignal.unity.ios/Runtime/OneSignaliOS.cs‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ public override bool ConsentRequired
143143

144144
public override void Initialize(string appId)
145145
{
146+
if (InputGuard.IsMissing(appId, "initialize: appId"))
147+
return;
146148
_oneSignalInitialize(appId);
147149

148150
if (_inAppMessages == null)
@@ -183,7 +185,9 @@ public override void Initialize(string appId)
183185

184186
public override void Login(string externalId, string jwtBearerToken = null)
185187
{
186-
if (jwtBearerToken == null)
188+
if (InputGuard.IsMissing(externalId, "login: externalId"))
189+
return;
190+
if (string.IsNullOrWhiteSpace(jwtBearerToken))
187191
{
188192
_oneSignalLogin(externalId);
189193
}

0 commit comments

Comments
 (0)