Simon Hunt
Committed by Gerrit Code Review

Harden DefaultApplication against modification.

Change-Id: I3342e4961096a49ad9761952d73b4a08ea0eebeb
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
15 */ 15 */
16 package org.onosproject.core; 16 package org.onosproject.core;
17 17
18 +import com.google.common.collect.ImmutableList;
19 +import com.google.common.collect.ImmutableSet;
18 import org.onosproject.security.Permission; 20 import org.onosproject.security.Permission;
19 21
20 import java.net.URI; 22 import java.net.URI;
...@@ -76,12 +78,18 @@ public class DefaultApplication implements Application { ...@@ -76,12 +78,18 @@ public class DefaultApplication implements Application {
76 this.category = checkNotNull(category, "Category cannot be null"); 78 this.category = checkNotNull(category, "Category cannot be null");
77 this.url = url; 79 this.url = url;
78 this.readme = checkNotNull(readme, "Readme cannot be null"); 80 this.readme = checkNotNull(readme, "Readme cannot be null");
79 - this.icon = icon; 81 + this.icon = icon == null ? new byte[0] : icon.clone();
80 this.role = checkNotNull(role, "Role cannot be null"); 82 this.role = checkNotNull(role, "Role cannot be null");
81 - this.permissions = checkNotNull(permissions, "Permissions cannot be null"); 83 + this.permissions = ImmutableSet.copyOf(
84 + checkNotNull(permissions, "Permissions cannot be null")
85 + );
82 this.featuresRepo = checkNotNull(featuresRepo, "Features repo cannot be null"); 86 this.featuresRepo = checkNotNull(featuresRepo, "Features repo cannot be null");
83 - this.features = checkNotNull(features, "Features cannot be null"); 87 + this.features = ImmutableList.copyOf(
84 - this.requiredApps = checkNotNull(requiredApps, "Required apps cannot be null"); 88 + checkNotNull(features, "Features cannot be null")
89 + );
90 + this.requiredApps = ImmutableList.copyOf(
91 + checkNotNull(requiredApps, "Required apps cannot be null")
92 + );
85 checkArgument(!features.isEmpty(), "There must be at least one feature"); 93 checkArgument(!features.isEmpty(), "There must be at least one feature");
86 } 94 }
87 95
...@@ -117,7 +125,7 @@ public class DefaultApplication implements Application { ...@@ -117,7 +125,7 @@ public class DefaultApplication implements Application {
117 125
118 @Override 126 @Override
119 public byte[] icon() { 127 public byte[] icon() {
120 - return icon; 128 + return icon.clone();
121 } 129 }
122 130
123 @Override 131 @Override
......
...@@ -15,12 +15,22 @@ ...@@ -15,12 +15,22 @@
15 */ 15 */
16 package org.onosproject.core; 16 package org.onosproject.core;
17 17
18 +import com.google.common.collect.ImmutableList;
19 +import com.google.common.collect.ImmutableSet;
18 import com.google.common.testing.EqualsTester; 20 import com.google.common.testing.EqualsTester;
19 import org.junit.Test; 21 import org.junit.Test;
22 +import org.onosproject.security.AppPermission;
23 +import org.onosproject.security.Permission;
20 24
25 +import java.util.ArrayList;
26 +import java.util.HashSet;
27 +import java.util.List;
21 import java.util.Optional; 28 import java.util.Optional;
29 +import java.util.Set;
22 30
31 +import static org.junit.Assert.assertArrayEquals;
23 import static org.junit.Assert.assertEquals; 32 import static org.junit.Assert.assertEquals;
33 +import static org.junit.Assert.assertNotNull;
24 import static org.junit.Assert.assertTrue; 34 import static org.junit.Assert.assertTrue;
25 import static org.onosproject.app.DefaultApplicationDescriptionTest.*; 35 import static org.onosproject.app.DefaultApplicationDescriptionTest.*;
26 36
...@@ -43,7 +53,7 @@ public class DefaultApplicationTest { ...@@ -43,7 +53,7 @@ public class DefaultApplicationTest {
43 assertEquals("incorrect category", CATEGORY, app.category()); 53 assertEquals("incorrect category", CATEGORY, app.category());
44 assertEquals("incorrect URL", URL, app.url()); 54 assertEquals("incorrect URL", URL, app.url());
45 assertEquals("incorrect readme", README, app.readme()); 55 assertEquals("incorrect readme", README, app.readme());
46 - assertEquals("incorrect icon", ICON, app.icon()); 56 + assertArrayEquals("incorrect icon", ICON, app.icon());
47 assertEquals("incorrect role", ROLE, app.role()); 57 assertEquals("incorrect role", ROLE, app.role());
48 assertEquals("incorrect permissions", PERMS, app.permissions()); 58 assertEquals("incorrect permissions", PERMS, app.permissions());
49 assertEquals("incorrect features repo", FURL, app.featuresRepo().get()); 59 assertEquals("incorrect features repo", FURL, app.featuresRepo().get());
...@@ -69,4 +79,138 @@ public class DefaultApplicationTest { ...@@ -69,4 +79,138 @@ public class DefaultApplicationTest {
69 new EqualsTester().addEqualityGroup(a1, a2) 79 new EqualsTester().addEqualityGroup(a1, a2)
70 .addEqualityGroup(a3).addEqualityGroup(a4).testEquals(); 80 .addEqualityGroup(a3).addEqualityGroup(a4).testEquals();
71 } 81 }
82 +
83 +
84 + private static final byte[] ICON_ORIG = new byte[] {1, 2, 3, 4};
85 +
86 + @Test
87 + public void immutableIcon() {
88 + byte[] iconSourceData = ICON_ORIG.clone();
89 +
90 + Application app = new DefaultApplication(APP_ID, VER, DESC, ORIGIN,
91 + CATEGORY, URL, README, iconSourceData, ROLE,
92 + PERMS, Optional.of(FURL), FEATURES, APPS);
93 +
94 + // can we modify the icon after getting a reference to the app?
95 + byte[] icon = app.icon();
96 + assertArrayEquals("did not start with orig icon", ICON_ORIG, icon);
97 +
98 + // now the hack
99 + for (int i = 0, n = ICON_ORIG.length; i < n; i++) {
100 + icon[i] = 0;
101 + }
102 + // if the reference to the internal array is given out, the hack
103 + // will succeed and this next assertion fails
104 + assertArrayEquals("no longer orig icon", ICON_ORIG, app.icon());
105 +
106 + // what if we modify the source data?
107 + for (int i = 0, n = ICON_ORIG.length; i < n; i++) {
108 + iconSourceData[i] = 0;
109 + }
110 + // if the application just saved a reference to the given array
111 + // this next assertion fails
112 + assertArrayEquals("modifying source alters appicon", ICON_ORIG, app.icon());
113 + }
114 +
115 + private static final Permission PERM_W =
116 + new Permission(AppPermission.class.getName(), "FLOWRULE_WRITE");
117 + private static final Permission PERM_R =
118 + new Permission(AppPermission.class.getName(), "FLOWRULE_READ");
119 +
120 + private static final Permission JUNK_PERM = new Permission("foo", "bar");
121 +
122 + private static final Set<Permission> PERMS_ORIG = ImmutableSet.of(PERM_W, PERM_R);
123 + private static final Set<Permission> PERMS_UNSAFE = new HashSet<>(PERMS_ORIG);
124 +
125 +
126 + @Test
127 + public void immutablePermissions() {
128 +// Set<Permission> p = PERMS_ORIG;
129 + Set<Permission> p = PERMS_UNSAFE;
130 +
131 + Application app = new DefaultApplication(APP_ID, VER, DESC, ORIGIN,
132 + CATEGORY, URL, README, ICON, ROLE,
133 + p, Optional.of(FURL), FEATURES, APPS);
134 +
135 + Set<Permission> perms = app.permissions();
136 + try {
137 + perms.add(JUNK_PERM);
138 + } catch (UnsupportedOperationException e) {
139 + // set is immutable
140 + }
141 + assertTrue("no write perm", app.permissions().contains(PERM_W));
142 + assertTrue("no read perm", app.permissions().contains(PERM_R));
143 + assertEquals("extra perms", 2, app.permissions().size());
144 +
145 + // DONE: review - is it sufficient to expect caller to pass in ImmutableSet ?
146 + // Issue Resolved with Immutable collections used during construction.
147 +
148 + // If we just pass in a HashSet, the contents would be modifiable by
149 + // an external party. (Making the field final just means that the
150 + // reference to the set can never change; the contents may still...)
151 +
152 + // Similar reasoning can be applied to these two fields also:
153 + // List<String> features
154 + // List<String> requiredApps
155 + }
156 +
157 + private static final String FOO = "foo";
158 + private static final String BAR = "bar";
159 + private static final String FIFI = "fifi";
160 + private static final String EVIL = "Bwahahahaha!";
161 +
162 + private static final List<String> FEATURES_ORIG = ImmutableList.of(FOO, BAR);
163 + private static final List<String> FEATURES_UNSAFE = new ArrayList<>(FEATURES_ORIG);
164 +
165 + private static final List<String> REQ_APPS_ORIG = ImmutableList.of(FIFI);
166 + private static final List<String> REQ_APPS_UNSAFE = new ArrayList<>(REQ_APPS_ORIG);
167 +
168 + @Test
169 + public void immutableFeatures() {
170 +// List<String> f = FEATURES_ORIG;
171 + List<String> f = FEATURES_UNSAFE;
172 +
173 + Application app = new DefaultApplication(APP_ID, VER, DESC, ORIGIN,
174 + CATEGORY, URL, README, ICON, ROLE,
175 + PERMS, Optional.of(FURL), f, APPS);
176 +
177 + List<String> features = app.features();
178 + try {
179 + features.add(EVIL);
180 + } catch (UnsupportedOperationException e) {
181 + // list is immutable
182 + }
183 + assertTrue("no foo feature", features.contains(FOO));
184 + assertTrue("no bar feature", features.contains(BAR));
185 + assertEquals("extra features!", 2, features.size());
186 + }
187 +
188 + @Test
189 + public void immutableRequiredApps() {
190 +// List<String> ra = REQ_APPS_ORIG;
191 + List<String> ra = REQ_APPS_UNSAFE;
192 +
193 + Application app = new DefaultApplication(APP_ID, VER, DESC, ORIGIN,
194 + CATEGORY, URL, README, ICON, ROLE,
195 + PERMS, Optional.of(FURL), FEATURES, ra);
196 +
197 + List<String> reqApps = app.requiredApps();
198 + try {
199 + reqApps.add(EVIL);
200 + } catch (UnsupportedOperationException e) {
201 + // list is immutable
202 + }
203 + assertTrue("no fifi required app", reqApps.contains(FIFI));
204 + assertEquals("extra required apps!", 1, reqApps.size());
205 + }
206 +
207 + @Test
208 + public void nullIcon() {
209 + Application app = new DefaultApplication(APP_ID, VER, DESC, ORIGIN,
210 + CATEGORY, URL, README, null, ROLE,
211 + PERMS, Optional.of(FURL), FEATURES, APPS);
212 + byte[] icon = app.icon();
213 + assertNotNull("null icon", icon);
214 + assertEquals("unexpected size", 0, icon.length);
215 + }
72 } 216 }
...\ No newline at end of file ...\ No newline at end of file
......