diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index 348a229047cc0b49fc23177a2d1541b523d80d2c..c826dfd9675e0eb198b647a820c571a0550cee27 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -254,11 +254,52 @@ static ResultCode SetMemoryPermission(VAddr addr, u64 size, u32 prot) {
     return vm_manager.ReprotectRange(addr, size, converted_permissions);
 }
 
-static ResultCode SetMemoryAttribute(VAddr addr, u64 size, u32 state0, u32 state1) {
-    LOG_WARNING(Kernel_SVC,
-                "(STUBBED) called, addr=0x{:X}, size=0x{:X}, state0=0x{:X}, state1=0x{:X}", addr,
-                size, state0, state1);
-    return RESULT_SUCCESS;
+static ResultCode SetMemoryAttribute(VAddr address, u64 size, u32 mask, u32 attribute) {
+    LOG_DEBUG(Kernel_SVC,
+              "called, address=0x{:016X}, size=0x{:X}, mask=0x{:08X}, attribute=0x{:08X}", address,
+              size, mask, attribute);
+
+    if (!Common::Is4KBAligned(address)) {
+        LOG_ERROR(Kernel_SVC, "Address not page aligned (0x{:016X})", address);
+        return ERR_INVALID_ADDRESS;
+    }
+
+    if (size == 0 || !Common::Is4KBAligned(size)) {
+        LOG_ERROR(Kernel_SVC, "Invalid size (0x{:X}). Size must be non-zero and page aligned.",
+                  size);
+        return ERR_INVALID_ADDRESS;
+    }
+
+    if (!IsValidAddressRange(address, size)) {
+        LOG_ERROR(Kernel_SVC, "Address range overflowed (Address: 0x{:016X}, Size: 0x{:016X})",
+                  address, size);
+        return ERR_INVALID_ADDRESS_STATE;
+    }
+
+    const auto mem_attribute = static_cast<MemoryAttribute>(attribute);
+    const auto mem_mask = static_cast<MemoryAttribute>(mask);
+    const auto attribute_with_mask = mem_attribute | mem_mask;
+
+    if (attribute_with_mask != mem_mask) {
+        LOG_ERROR(Kernel_SVC,
+                  "Memory attribute doesn't match the given mask (Attribute: 0x{:X}, Mask: {:X}",
+                  attribute, mask);
+        return ERR_INVALID_COMBINATION;
+    }
+
+    if ((attribute_with_mask | MemoryAttribute::Uncached) != MemoryAttribute::Uncached) {
+        LOG_ERROR(Kernel_SVC, "Specified attribute isn't equal to MemoryAttributeUncached (8).");
+        return ERR_INVALID_COMBINATION;
+    }
+
+    auto& vm_manager = Core::CurrentProcess()->VMManager();
+    if (!IsInsideAddressSpace(vm_manager, address, size)) {
+        LOG_ERROR(Kernel_SVC,
+                  "Given address (0x{:016X}) is outside the bounds of the address space.", address);
+        return ERR_INVALID_ADDRESS_STATE;
+    }
+
+    return vm_manager.SetMemoryAttribute(address, size, mem_mask, mem_attribute);
 }
 
 /// Maps a memory range into a different range.
diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp
index d3b55a51ec932facc1c53f05cc6c4e8504fabaed..f39e096ca4cfe56ca117600a3d9663720915fdf2 100644
--- a/src/core/hle/kernel/vm_manager.cpp
+++ b/src/core/hle/kernel/vm_manager.cpp
@@ -37,7 +37,7 @@ static const char* GetMemoryStateName(MemoryState state) {
 
 bool VirtualMemoryArea::CanBeMergedWith(const VirtualMemoryArea& next) const {
     ASSERT(base + size == next.base);
-    if (permissions != next.permissions || meminfo_state != next.meminfo_state ||
+    if (permissions != next.permissions || state != next.state || attribute != next.attribute ||
         type != next.type) {
         return false;
     }
@@ -115,7 +115,7 @@ ResultVal<VMManager::VMAHandle> VMManager::MapMemoryBlock(VAddr target,
 
     final_vma.type = VMAType::AllocatedMemoryBlock;
     final_vma.permissions = VMAPermission::ReadWrite;
-    final_vma.meminfo_state = state;
+    final_vma.state = state;
     final_vma.backing_block = std::move(block);
     final_vma.offset = offset;
     UpdatePageTableForVMA(final_vma);
@@ -140,7 +140,7 @@ ResultVal<VMManager::VMAHandle> VMManager::MapBackingMemory(VAddr target, u8* me
 
     final_vma.type = VMAType::BackingMemory;
     final_vma.permissions = VMAPermission::ReadWrite;
-    final_vma.meminfo_state = state;
+    final_vma.state = state;
     final_vma.backing_memory = memory;
     UpdatePageTableForVMA(final_vma);
 
@@ -177,7 +177,7 @@ ResultVal<VMManager::VMAHandle> VMManager::MapMMIO(VAddr target, PAddr paddr, u6
 
     final_vma.type = VMAType::MMIO;
     final_vma.permissions = VMAPermission::ReadWrite;
-    final_vma.meminfo_state = state;
+    final_vma.state = state;
     final_vma.paddr = paddr;
     final_vma.mmio_handler = std::move(mmio_handler);
     UpdatePageTableForVMA(final_vma);
@@ -189,7 +189,7 @@ VMManager::VMAIter VMManager::Unmap(VMAIter vma_handle) {
     VirtualMemoryArea& vma = vma_handle->second;
     vma.type = VMAType::Free;
     vma.permissions = VMAPermission::None;
-    vma.meminfo_state = MemoryState::Unmapped;
+    vma.state = MemoryState::Unmapped;
 
     vma.backing_block = nullptr;
     vma.offset = 0;
@@ -308,9 +308,10 @@ MemoryInfo VMManager::QueryMemory(VAddr address) const {
 
     if (IsValidHandle(vma)) {
         memory_info.base_address = vma->second.base;
+        memory_info.attributes = ToSvcMemoryAttribute(vma->second.attribute);
         memory_info.permission = static_cast<u32>(vma->second.permissions);
         memory_info.size = vma->second.size;
-        memory_info.state = ToSvcMemoryState(vma->second.meminfo_state);
+        memory_info.state = ToSvcMemoryState(vma->second.state);
     } else {
         memory_info.base_address = address_space_end;
         memory_info.permission = static_cast<u32>(VMAPermission::None);
@@ -321,6 +322,34 @@ MemoryInfo VMManager::QueryMemory(VAddr address) const {
     return memory_info;
 }
 
+ResultCode VMManager::SetMemoryAttribute(VAddr address, u64 size, MemoryAttribute mask,
+                                         MemoryAttribute attribute) {
+    constexpr auto ignore_mask = MemoryAttribute::Uncached | MemoryAttribute::DeviceMapped;
+    constexpr auto attribute_mask = ~ignore_mask;
+
+    const auto result = CheckRangeState(
+        address, size, MemoryState::FlagUncached, MemoryState::FlagUncached, VMAPermission::None,
+        VMAPermission::None, attribute_mask, MemoryAttribute::None, ignore_mask);
+
+    if (result.Failed()) {
+        return result.Code();
+    }
+
+    const auto [prev_state, prev_permissions, prev_attributes] = *result;
+    const auto new_attribute = (prev_attributes & ~mask) | (mask & attribute);
+
+    const auto carve_result = CarveVMARange(address, size);
+    if (carve_result.Failed()) {
+        return carve_result.Code();
+    }
+
+    auto vma_iter = *carve_result;
+    vma_iter->second.attribute = new_attribute;
+
+    MergeAdjacent(vma_iter);
+    return RESULT_SUCCESS;
+}
+
 ResultCode VMManager::MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, MemoryState state) {
     const auto vma = FindVMA(src_addr);
 
@@ -364,7 +393,7 @@ void VMManager::LogLayout() const {
                   (u8)vma.permissions & (u8)VMAPermission::Read ? 'R' : '-',
                   (u8)vma.permissions & (u8)VMAPermission::Write ? 'W' : '-',
                   (u8)vma.permissions & (u8)VMAPermission::Execute ? 'X' : '-',
-                  GetMemoryStateName(vma.meminfo_state));
+                  GetMemoryStateName(vma.state));
     }
 }
 
@@ -591,6 +620,66 @@ void VMManager::ClearPageTable() {
               Memory::PageType::Unmapped);
 }
 
+VMManager::CheckResults VMManager::CheckRangeState(VAddr address, u64 size, MemoryState state_mask,
+                                                   MemoryState state, VMAPermission permission_mask,
+                                                   VMAPermission permissions,
+                                                   MemoryAttribute attribute_mask,
+                                                   MemoryAttribute attribute,
+                                                   MemoryAttribute ignore_mask) const {
+    auto iter = FindVMA(address);
+
+    // If we don't have a valid VMA handle at this point, then it means this is
+    // being called with an address outside of the address space, which is definitely
+    // indicative of a bug, as this function only operates on mapped memory regions.
+    DEBUG_ASSERT(IsValidHandle(iter));
+
+    const VAddr end_address = address + size - 1;
+    const MemoryAttribute initial_attributes = iter->second.attribute;
+    const VMAPermission initial_permissions = iter->second.permissions;
+    const MemoryState initial_state = iter->second.state;
+
+    while (true) {
+        // The iterator should be valid throughout the traversal. Hitting the end of
+        // the mapped VMA regions is unquestionably indicative of a bug.
+        DEBUG_ASSERT(IsValidHandle(iter));
+
+        const auto& vma = iter->second;
+
+        if (vma.state != initial_state) {
+            return ERR_INVALID_ADDRESS_STATE;
+        }
+
+        if ((vma.state & state_mask) != state) {
+            return ERR_INVALID_ADDRESS_STATE;
+        }
+
+        if (vma.permissions != initial_permissions) {
+            return ERR_INVALID_ADDRESS_STATE;
+        }
+
+        if ((vma.permissions & permission_mask) != permissions) {
+            return ERR_INVALID_ADDRESS_STATE;
+        }
+
+        if ((vma.attribute | ignore_mask) != (initial_attributes | ignore_mask)) {
+            return ERR_INVALID_ADDRESS_STATE;
+        }
+
+        if ((vma.attribute & attribute_mask) != attribute) {
+            return ERR_INVALID_ADDRESS_STATE;
+        }
+
+        if (end_address <= vma.EndAddress()) {
+            break;
+        }
+
+        ++iter;
+    }
+
+    return MakeResult(
+        std::make_tuple(initial_state, initial_permissions, initial_attributes & ~ignore_mask));
+}
+
 u64 VMManager::GetTotalMemoryUsage() const {
     LOG_WARNING(Kernel, "(STUBBED) called");
     return 0xF8000000;
diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h
index 10bacac3e4a15925d57369d186774e1bb6b2949b..6091533bca7dea0f7066b592bd0c3ef0440708e3 100644
--- a/src/core/hle/kernel/vm_manager.h
+++ b/src/core/hle/kernel/vm_manager.h
@@ -6,6 +6,7 @@
 
 #include <map>
 #include <memory>
+#include <tuple>
 #include <vector>
 #include "common/common_types.h"
 #include "core/hle/result.h"
@@ -43,6 +44,88 @@ enum class VMAPermission : u8 {
     ReadWriteExecute = Read | Write | Execute,
 };
 
+constexpr VMAPermission operator|(VMAPermission lhs, VMAPermission rhs) {
+    return static_cast<VMAPermission>(u32(lhs) | u32(rhs));
+}
+
+constexpr VMAPermission operator&(VMAPermission lhs, VMAPermission rhs) {
+    return static_cast<VMAPermission>(u32(lhs) & u32(rhs));
+}
+
+constexpr VMAPermission operator^(VMAPermission lhs, VMAPermission rhs) {
+    return static_cast<VMAPermission>(u32(lhs) ^ u32(rhs));
+}
+
+constexpr VMAPermission operator~(VMAPermission permission) {
+    return static_cast<VMAPermission>(~u32(permission));
+}
+
+constexpr VMAPermission& operator|=(VMAPermission& lhs, VMAPermission rhs) {
+    lhs = lhs | rhs;
+    return lhs;
+}
+
+constexpr VMAPermission& operator&=(VMAPermission& lhs, VMAPermission rhs) {
+    lhs = lhs & rhs;
+    return lhs;
+}
+
+constexpr VMAPermission& operator^=(VMAPermission& lhs, VMAPermission rhs) {
+    lhs = lhs ^ rhs;
+    return lhs;
+}
+
+/// Attribute flags that can be applied to a VMA
+enum class MemoryAttribute : u32 {
+    Mask = 0xFF,
+
+    /// No particular qualities
+    None = 0,
+    /// Memory locked/borrowed for use. e.g. This would be used by transfer memory.
+    Locked = 1,
+    /// Memory locked for use by IPC-related internals.
+    LockedForIPC = 2,
+    /// Mapped as part of the device address space.
+    DeviceMapped = 4,
+    /// Uncached memory
+    Uncached = 8,
+};
+
+constexpr MemoryAttribute operator|(MemoryAttribute lhs, MemoryAttribute rhs) {
+    return static_cast<MemoryAttribute>(u32(lhs) | u32(rhs));
+}
+
+constexpr MemoryAttribute operator&(MemoryAttribute lhs, MemoryAttribute rhs) {
+    return static_cast<MemoryAttribute>(u32(lhs) & u32(rhs));
+}
+
+constexpr MemoryAttribute operator^(MemoryAttribute lhs, MemoryAttribute rhs) {
+    return static_cast<MemoryAttribute>(u32(lhs) ^ u32(rhs));
+}
+
+constexpr MemoryAttribute operator~(MemoryAttribute attribute) {
+    return static_cast<MemoryAttribute>(~u32(attribute));
+}
+
+constexpr MemoryAttribute& operator|=(MemoryAttribute& lhs, MemoryAttribute rhs) {
+    lhs = lhs | rhs;
+    return lhs;
+}
+
+constexpr MemoryAttribute& operator&=(MemoryAttribute& lhs, MemoryAttribute rhs) {
+    lhs = lhs & rhs;
+    return lhs;
+}
+
+constexpr MemoryAttribute& operator^=(MemoryAttribute& lhs, MemoryAttribute rhs) {
+    lhs = lhs ^ rhs;
+    return lhs;
+}
+
+constexpr u32 ToSvcMemoryAttribute(MemoryAttribute attribute) {
+    return static_cast<u32>(attribute & MemoryAttribute::Mask);
+}
+
 // clang-format off
 /// Represents memory states and any relevant flags, as used by the kernel.
 /// svcQueryMemory interprets these by masking away all but the first eight
@@ -174,6 +257,16 @@ struct PageInfo {
  * also backed by a single host memory allocation.
  */
 struct VirtualMemoryArea {
+    /// Gets the starting (base) address of this VMA.
+    VAddr StartAddress() const {
+        return base;
+    }
+
+    /// Gets the ending address of this VMA.
+    VAddr EndAddress() const {
+        return base + size - 1;
+    }
+
     /// Virtual base address of the region.
     VAddr base = 0;
     /// Size of the region.
@@ -181,8 +274,8 @@ struct VirtualMemoryArea {
 
     VMAType type = VMAType::Free;
     VMAPermission permissions = VMAPermission::None;
-    /// Tag returned by svcQueryMemory. Not otherwise used.
-    MemoryState meminfo_state = MemoryState::Unmapped;
+    MemoryState state = MemoryState::Unmapped;
+    MemoryAttribute attribute = MemoryAttribute::None;
 
     // Settings for type = AllocatedMemoryBlock
     /// Memory block backing this VMA.
@@ -299,6 +392,19 @@ public:
     ///
     MemoryInfo QueryMemory(VAddr address) const;
 
+    /// Sets an attribute across the given address range.
+    ///
+    /// @param address   The starting address
+    /// @param size      The size of the range to set the attribute on.
+    /// @param mask      The attribute mask
+    /// @param attribute The attribute to set across the given address range
+    ///
+    /// @returns RESULT_SUCCESS if successful
+    /// @returns ERR_INVALID_ADDRESS_STATE if the attribute could not be set.
+    ///
+    ResultCode SetMemoryAttribute(VAddr address, u64 size, MemoryAttribute mask,
+                                  MemoryAttribute attribute);
+
     /**
      * Scans all VMAs and updates the page table range of any that use the given vector as backing
      * memory. This should be called after any operation that causes reallocation of the vector.
@@ -435,6 +541,35 @@ private:
     /// Clears out the page table
     void ClearPageTable();
 
+    using CheckResults = ResultVal<std::tuple<MemoryState, VMAPermission, MemoryAttribute>>;
+
+    /// Checks if an address range adheres to the specified states provided.
+    ///
+    /// @param address         The starting address of the address range.
+    /// @param size            The size of the address range.
+    /// @param state_mask      The memory state mask.
+    /// @param state           The state to compare the individual VMA states against,
+    ///                        which is done in the form of: (vma.state & state_mask) != state.
+    /// @param permission_mask The memory permissions mask.
+    /// @param permissions     The permission to compare the individual VMA permissions against,
+    ///                        which is done in the form of:
+    ///                        (vma.permission & permission_mask) != permission.
+    /// @param attribute_mask  The memory attribute mask.
+    /// @param attribute       The memory attributes to compare the individual VMA attributes
+    ///                        against, which is done in the form of:
+    ///                        (vma.attributes & attribute_mask) != attribute.
+    /// @param ignore_mask     The memory attributes to ignore during the check.
+    ///
+    /// @returns If successful, returns a tuple containing the memory attributes
+    ///          (with ignored bits specified by ignore_mask unset), memory permissions, and
+    ///          memory state across the memory range.
+    /// @returns If not successful, returns ERR_INVALID_ADDRESS_STATE.
+    ///
+    CheckResults CheckRangeState(VAddr address, u64 size, MemoryState state_mask, MemoryState state,
+                                 VMAPermission permission_mask, VMAPermission permissions,
+                                 MemoryAttribute attribute_mask, MemoryAttribute attribute,
+                                 MemoryAttribute ignore_mask) const;
+
     /**
      * A map covering the entirety of the managed address space, keyed by the `base` field of each
      * VMA. It must always be modified by splitting or merging VMAs, so that the invariant