[ 
https://issues.apache.org/jira/browse/CLOUDSTACK-9593?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=16305264#comment-16305264
 ] 

ASF GitHub Bot commented on CLOUDSTACK-9593:
--------------------------------------------

rhtyd closed pull request #1760: CLOUDSTACK-9593: userdata: enforce data is a 
multiple of 4 characters
URL: https://github.com/apache/cloudstack/pull/1760
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/engine/schema/src/com/cloud/upgrade/dao/Upgrade41000to41100.java 
b/engine/schema/src/com/cloud/upgrade/dao/Upgrade41000to41100.java
index fbe9d784432..6afd976e7e2 100644
--- a/engine/schema/src/com/cloud/upgrade/dao/Upgrade41000to41100.java
+++ b/engine/schema/src/com/cloud/upgrade/dao/Upgrade41000to41100.java
@@ -27,6 +27,7 @@
 import java.util.Map;
 import java.util.Set;
 
+import org.apache.commons.codec.binary.Base64;
 import org.apache.log4j.Logger;
 
 import com.cloud.hypervisor.Hypervisor;
@@ -64,9 +65,49 @@ public boolean supportsRollingUpgrade() {
 
     @Override
     public void performDataMigration(Connection conn) {
+        validateUserDataInBase64(conn);
         updateSystemVmTemplates(conn);
     }
 
+    private void validateUserDataInBase64(Connection conn) {
+        try (final PreparedStatement selectStatement = 
conn.prepareStatement("SELECT `id`, `user_data` FROM `cloud`.`user_vm` WHERE 
`user_data` IS NOT NULL;");
+             final ResultSet selectResultSet = selectStatement.executeQuery()) 
{
+            while (selectResultSet.next()) {
+                final Long userVmId = selectResultSet.getLong(1);
+                final String userData = selectResultSet.getString(2);
+                if (Base64.isBase64(userData)) {
+                    final String newUserData = 
Base64.encodeBase64String(Base64.decodeBase64(userData.getBytes()));
+                    if (!userData.equals(newUserData)) {
+                        try (final PreparedStatement updateStatement = 
conn.prepareStatement("UPDATE `cloud`.`user_vm` SET `user_data` = ? WHERE `id` 
= ? ;")) {
+                            updateStatement.setString(1, newUserData);
+                            updateStatement.setLong(2, userVmId);
+                            updateStatement.executeUpdate();
+                        } catch (SQLException e) {
+                            LOG.error("Failed to update cloud.user_vm 
user_data for id:" + userVmId + " with exception: " + e.getMessage());
+                            throw new CloudRuntimeException("Exception while 
updating cloud.user_vm for id " + userVmId, e);
+                        }
+                    }
+                } else {
+                    // Update to NULL since it's invalid
+                    LOG.warn("Removing user_data for vm id " + userVmId + " 
because it's invalid");
+                    LOG.warn("Removed data was: " + userData);
+                    try (final PreparedStatement updateStatement = 
conn.prepareStatement("UPDATE `cloud`.`user_vm` SET `user_data` = NULL WHERE 
`id` = ? ;")) {
+                        updateStatement.setLong(1, userVmId);
+                        updateStatement.executeUpdate();
+                    } catch (SQLException e) {
+                        LOG.error("Failed to update cloud.user_vm user_data 
for id:" + userVmId + " to NULL with exception: " + e.getMessage());
+                        throw new CloudRuntimeException("Exception while 
updating cloud.user_vm for id " + userVmId + " to NULL", e);
+                    }
+                }
+            }
+        } catch (SQLException e) {
+            throw new CloudRuntimeException("Exception while validating 
existing user_vm table's user_data column to be base64 valid with padding", e);
+        }
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("Done validating base64 content of user data");
+        }
+    }
+
     @SuppressWarnings("serial")
     private void updateSystemVmTemplates(final Connection conn) {
         LOG.debug("Updating System Vm template IDs");
diff --git a/server/src/com/cloud/vm/UserVmManagerImpl.java 
b/server/src/com/cloud/vm/UserVmManagerImpl.java
index 3591e603b0a..cd725b1b6ab 100644
--- a/server/src/com/cloud/vm/UserVmManagerImpl.java
+++ b/server/src/com/cloud/vm/UserVmManagerImpl.java
@@ -2525,7 +2525,7 @@ public UserVm updateVirtualMachine(long id, String 
displayName, String group, Bo
         if (userData != null) {
             // check and replace newlines
             userData = userData.replace("\\n", "");
-            validateUserData(userData, httpMethod);
+            userData = validateUserData(userData, httpMethod);
             // update userData on domain router.
             updateUserdata = true;
         } else {
@@ -3395,7 +3395,7 @@ protected UserVm createVirtualMachine(DataCenter zone, 
ServiceOffering serviceOf
         _accountMgr.checkAccess(owner, AccessType.UseEntry, false, template);
 
         // check if the user data is correct
-        validateUserData(userData, httpmethod);
+        userData = validateUserData(userData, httpmethod);
 
         // Find an SSH public key corresponding to the key pair name, if one is
         // given
@@ -3942,7 +3942,7 @@ public void 
doInTransactionWithoutResult(TransactionStatus status) {
         }
     }
 
-    private void validateUserData(String userData, HTTPMethod httpmethod) {
+    protected String validateUserData(String userData, HTTPMethod httpmethod) {
         byte[] decodedUserData = null;
         if (userData != null) {
             if (!Base64.isBase64(userData)) {
@@ -3970,7 +3970,10 @@ private void validateUserData(String userData, 
HTTPMethod httpmethod) {
             if (decodedUserData == null || decodedUserData.length < 1) {
                 throw new InvalidParameterValueException("User data is too 
short");
             }
+            // Re-encode so that the '=' paddings are added if necessary since 
'isBase64' does not require it, but python does on the VR.
+            return Base64.encodeBase64String(decodedUserData);
         }
+        return null;
     }
 
     @Override
diff --git a/server/test/com/cloud/vm/UserVmManagerTest.java 
b/server/test/com/cloud/vm/UserVmManagerTest.java
index 1bab84cc36c..89555a2c8c8 100644
--- a/server/test/com/cloud/vm/UserVmManagerTest.java
+++ b/server/test/com/cloud/vm/UserVmManagerTest.java
@@ -48,6 +48,7 @@
 import com.cloud.event.dao.UsageEventDao;
 import com.cloud.uservm.UserVm;
 import org.junit.Assert;
+import org.apache.cloudstack.api.BaseCmd;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
@@ -1056,4 +1057,16 @@ public void testPersistDeviceBusInfo() {
         _userVmMgr.persistDeviceBusInfo(_vmMock, "lsilogic");
         verify(_vmDao, times(1)).saveDetails(any(UserVmVO.class));
     }
+
+    @Test
+    public void testValideBase64WithoutPadding() {
+        // fo should be encoded in base64 either as Zm8 or Zm8=
+        String encodedUserdata = "Zm8";
+        String encodedUserdataWithPadding = "Zm8=";
+
+        // Verify that we accept both but return the padded version
+        assertTrue("validate return the value with padding", 
encodedUserdataWithPadding.equals(_userVmMgr.validateUserData(encodedUserdata, 
BaseCmd.HTTPMethod.GET)));
+        assertTrue("validate return the value with padding", 
encodedUserdataWithPadding.equals(_userVmMgr.validateUserData(encodedUserdataWithPadding,
 BaseCmd.HTTPMethod.GET)));
+    }
+
 }


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
us...@infra.apache.org


> User data check is inconsistent with python
> -------------------------------------------
>
>                 Key: CLOUDSTACK-9593
>                 URL: https://issues.apache.org/jira/browse/CLOUDSTACK-9593
>             Project: CloudStack
>          Issue Type: Bug
>      Security Level: Public(Anyone can view this level - this is the 
> default.) 
>    Affects Versions: 4.4.2, 4.4.3, 4.3.2, 4.5.1, 4.4.4, 4.5.2, 4.6.0, 4.6.1, 
> 4.6.2, 4.7.0, 4.7.1, 4.8.0, 4.9.0
>            Reporter: Marc-Aurèle Brothier
>            Assignee: Marc-Aurèle Brothier
>
> The user data is validated through the Apache commons codec library, but this 
> library does not check that the length is a multiple of 4 characters. The RFC 
> does not require it either. But the python script in the virtual router that 
> loads the user data does check for the possible padding presence, requiring 
> the string to be a multiple of 4 characters.
> {code:python}
> >>> import base64
> >>> base64.b64decode('foo')
> Traceback (most recent call last):
>   File "<stdin>", line 1, in <module>
>   File 
> "/usr/local/Cellar/python/2.7.12/Frameworks/Python.framework/Versions/2.7/lib/python2.7/base64.py",
>  line 78, in b64decode
>     raise TypeError(msg)
> TypeError: Incorrect padding
> >>> base64.b64decode('foo=')
> '~\x8a'
> {code}
> Currently since the java check is less restrictive, the user data gets saved 
> into the database but the VR script crashes when it receives this VM user 
> data. On a single VM it is not really a problem. The critical issue is when a 
> VR is restarted. The invalid pythonic base64 string makes the vmdata.py 
> script crashed, resulting in a VR not starting at all.



--
This message was sent by Atlassian JIRA
(v6.4.14#64029)

Reply via email to