Hi!

I've put together a DMA command verifier for the VIA drm. It restricts access to DMA registers and system and AGP memory while 3D commands and 2D / Mpeg is allowed to pass.

Currently frame-buffer memory is considered all authenticated client's property, but I've added provisions to check destination and z-buffer memory addresses, but that would require some interoperation with the via DRM memory manager. Since the frame-buffer is read-write this currently is an overkill.

Textures in AGP memory is currently not allowed, but they are disabled in the Mesa driver as well, for some reason. If they are allowed in the future, Texture address checks probably have to be implemented but that should be a minor task.

The verifier is reasonably fast. It lowers the glxgears frame-rate a percent or two. However if it operates directly on AGP memory, it's a _real_ performance killer. So the userspace command buffer should be copied to kernel (static) system memory, checked and then copied again to the AGP ring-buffer.

The file can be directly compiled in with the Mesa driver as well since that makes debugging a lot easier.

Not all 3D functionality is in, but it is a start and it apparently works ok with the current Mesa unichrome driver. I've tested glxgears, chromium, tuxracer and the GL xscreensavers.

Comments are appreciated, particularly on the security assumptions and whether something doesn't fit well into the DRM coding style.

/Thomas


/*
 * Copyright 2004 The Unichrome Project. All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sub license,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial portions
 * of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
 * THE UNICHROME PROJECT, AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 * Author: Thomas Hellstr�m 2004.
 * This code was written using docs obtained under NDA from VIA Inc.
 */


#include "via_3d_reg.h"

#ifndef __KERNEL__
/*
 * For debugging purposes
 */

#include <stdio.h>
#include <errno.h>
typedef unsigned uint32_t;
#define DRM_ERROR(fmt, arg...) fprintf(stderr, fmt, ##arg)
#define DRM_DEBUG(fmt, arg...) printf(fmt, ##arg) 
#define DRM_ERR(arg) -(arg)
#else
#include "via.h"
#include "drmP.h"
#endif


typedef enum{
	state_command,
	state_header2,
	state_header1,
	state_error
} verifier_state_t;

typedef enum{
  no_sequence = 0, 
  z_address,
  dest_address,
  tex_address
}sequence_t;


typedef enum{
	no_check = 0,
	check_for_header2,
	check_for_header1,
	check_for_header2_err,
	check_for_header1_err,
	check_for_fire,
	check_z_buffer_addr,
	check_z_buffer_addr_mode,
	check_destination_addr,
	check_destination_addr_mode,
	check_for_dummy,
	check_for_dd,
	check_texture_addr,
	check_texture_addr_mode,
	forbidden_command
}hazard_t;

/*
 * Associates each hazard above with a possible multi-command
 * sequence. For example and address that is split over multiple
 * commands and needs to be checked at the first command that does
 * not include any part of the address.
 */

static sequence_t seqs[] =
  { no_sequence,
    no_sequence,
    no_sequence,
    no_sequence,
    no_sequence,
    no_sequence,
    z_address,
    z_address,
    dest_address,
    dest_address,
    no_sequence,
    no_sequence,
    tex_address,
    tex_address,
    no_sequence
  };
    
typedef struct{
	unsigned int code;
	hazard_t hz;
} hz_init_t;



static hz_init_t init_table1[] = 
  {{0xf2, check_for_header2_err},
   {0xf0, check_for_header1_err},
   {0xee, check_for_fire},
   {0xcc, check_for_dummy},
   {0xdd, check_for_dd},
   {0x00, no_check},
   {0x10, check_z_buffer_addr},
   {0x11, check_z_buffer_addr},
   {0x12, check_z_buffer_addr_mode},
   {0x13, no_check},
   {0x14, no_check},
   {0x15, no_check},
   {0x23, no_check},
   {0x24, no_check},
   {0x33, no_check},
   {0x34, no_check},
   {0x35, no_check},
   {0x36, no_check},
   {0x37, no_check},
   {0x38, no_check},
   {0x39, no_check},
   {0x3A, no_check},
   {0x3B, no_check},
   {0x3C, no_check},
   {0x3D, no_check},
   {0x3E, no_check},
   {0x40, check_destination_addr},
   {0x41, check_destination_addr},
   {0x42, check_destination_addr_mode},
   {0x43, no_check},
   {0x44, no_check},
   {0x50, no_check},
   {0x51, no_check},
   {0x52, no_check},
   {0x53, no_check},
   {0x54, no_check},
   {0x55, no_check},
   {0x56, no_check},
   {0x57, no_check},
   {0x58, no_check},
   {0x70, no_check},
   {0x71, no_check},
   {0x78, no_check},
   {0x79, no_check},
   {0x7A, no_check},
   {0x7B, no_check},
   {0x7C, no_check},
   {0x7D, no_check}};
   
   
		       
static hz_init_t init_table2[] = 
  {{0xf2, check_for_header2_err},
   {0xf0, check_for_header1_err},
   {0xee, check_for_fire},
   {0xcc, check_for_dummy},
   {0x00, check_texture_addr},
   {0x01, check_texture_addr},
   {0x02, check_texture_addr},
   {0x03, check_texture_addr},
   {0x04, check_texture_addr},
   {0x05, check_texture_addr},
   {0x06, check_texture_addr},
   {0x07, check_texture_addr},
   {0x08, check_texture_addr},
   {0x09, check_texture_addr},
   {0x20, check_texture_addr},
   {0x21, check_texture_addr},
   {0x22, check_texture_addr},
   {0x23, check_texture_addr},
   {0x2B, check_texture_addr},
   {0x2C, no_check},
   {0x2D, no_check},
   {0x2E, no_check},
   {0x2F, no_check},
   {0x30, no_check},
   {0x31, no_check},
   {0x32, no_check},
   {0x33, no_check},
   {0x34, no_check},
   {0x4B, no_check},
   {0x4C, no_check},
   {0x51, no_check},
   {0x52, no_check},
   {0x77, no_check},
   {0x78, no_check},
   {0x79, no_check},
   {0x7A, no_check},
   {0x7B, check_texture_addr_mode},
   {0x7C, no_check},
   {0x7D, no_check},
   {0x7E, no_check},
   {0x7F, no_check},
   {0x80, no_check},
   {0x81, no_check},
   {0x82, no_check},
   {0x83, no_check},
   {0x85, no_check},
   {0x86, no_check},
   {0x87, no_check},
   {0x88, no_check},
   {0x89, no_check},
   {0x8A, no_check},
   {0x90, no_check},
   {0x91, no_check},
   {0x92, no_check},
   {0x93, no_check}};

static hz_init_t init_table3[] = 
  {{0xf2, check_for_header2_err},
   {0xf0, check_for_header1_err},
   {0xcc, check_for_dummy},
   {0x00, no_check}};
   
		       
static hazard_t table1[256]; 
static hazard_t table2[256]; 
static hazard_t table3[256]; 



typedef struct{
	uint32_t z_addr, d_addr;
	sequence_t unfinished;
} sequence_context_t;

static sequence_context_t hc_sequence;

/*
 * This function does not currently do anything useful. In the future
 * it can be used to check that the context really owns the fb addresses
 * That will be set up by the command stream, and reject the stream if
 * the caller tries to use other memory areas.
 */

static __inline__ int
finish_current_sequence(sequence_context_t *cur_seq) 
{
	switch(cur_seq->unfinished) {
	case z_address:
		DRM_DEBUG("Z Buffer start address is 0x%x\n", cur_seq->z_addr);
		break;
	case dest_address:
		DRM_DEBUG("Destination start address is 0x%x\n", cur_seq->d_addr);
		break;
	default:
		break;
	}
	cur_seq->unfinished = no_sequence;
	return 0;
}

static __inline__ int 
investigate_hazard( uint32_t cmd, hazard_t hz, sequence_context_t *cur_seq)
{

	if (cur_seq->unfinished && (cur_seq->unfinished != seqs[hz])) {
		int ret;
		if ((ret = finish_current_sequence(cur_seq))) return ret;
	}

	switch(hz) {
	case check_for_header2:
		if (cmd == HALCYON_HEADER2) return 1;
		return 0;
	case check_for_header1:
		if ((cmd & HALCYON_HEADER1MASK) == HALCYON_HEADER1) return 1;
		return 0;
	case check_for_header2_err:
		if (cmd == HALCYON_HEADER2) return 1;
		DRM_ERROR("Illegal DMA HALCYON_HEADER2 command\n");
		break;
	case check_for_header1_err:
		if ((cmd & HALCYON_HEADER1MASK) == HALCYON_HEADER1) return 1;
		DRM_ERROR("Illegal DMA HALCYON_HEADER1 command\n");
		break;
	case check_for_fire:
		if ((cmd & HALCYON_FIREMASK) == HALCYON_FIRECMD) return 1; 
		DRM_ERROR("Illegal DMA HALCYON_FIRECMD command\n");
		break;
	case check_for_dummy:
		if (HC_DUMMY == cmd) return 0;
		DRM_ERROR("Illegal DMA HC_DUMMY command\n");
		break;
	case check_for_dd:
		if (0xdddddddd == cmd) return 0;
		DRM_ERROR("Illegal DMA 0xdddddddd command\n");
		break;
	case check_z_buffer_addr:
		cur_seq->unfinished = z_address;
		if ((cmd & 0xFF000000) == (0x10 << 24))
			cur_seq->z_addr = (cur_seq->z_addr & 0xFF000000) |
				(cmd & 0x00FFFFFF);
		else 
			cur_seq->z_addr = (cur_seq->z_addr & 0x00FFFFFF) |
				((cmd & 0xFF) << 24);
		return 0;
	case check_z_buffer_addr_mode:
		cur_seq->unfinished = z_address;
		if ((cmd & 0x0000C000) == 0) return 0;
		DRM_ERROR("Attempt to place Z buffer in system memory\n");
		return 2;
	case check_destination_addr:
		cur_seq->unfinished = dest_address;
		if ((cmd & 0xFF000000) == (0x40 << 24))
			cur_seq->d_addr = (cur_seq->d_addr & 0xFF000000) |
				(cmd & 0x00FFFFFF);
		else 
			cur_seq->d_addr = (cur_seq->d_addr & 0x00FFFFFF) |
				((cmd & 0xFF) << 24);
		return 0;
	case check_destination_addr_mode:
		cur_seq->unfinished = dest_address;
		if ((cmd & 0x0000C000) == 0) return 0;
		DRM_ERROR("Attempt to place 3D drawing buffer in system memory\n");
		return 2;	    
	case check_texture_addr:
		cur_seq->unfinished = tex_address;

		/*
		 * Here we could implement a check for texture addresses if we
		 * loosen the restriction that they should reside in frame buffer
		 * memory.
		 */

		return 0;
	case check_texture_addr_mode:
		cur_seq->unfinished = tex_address;
		if ((cmd & 0x00000003) == 0) return 0;
		DRM_ERROR("Attempt to fetch texture from system or AGP memory.\n");
		return 2;
	default:
		DRM_ERROR("Illegal DMA data: 0x%x\n", cmd);
		return 2;
	}
	return 2;
}



		
static __inline__ verifier_state_t
via_check_header2( uint32_t const **buffer, const uint32_t *buf_end )
{
	uint32_t cmd;
	int hz_mode;
	hazard_t hz;
	const uint32_t *buf = *buffer;
	const hazard_t *hz_table;

	if ((buf_end - buf) < 2) {
		DRM_ERROR("Illegal termination of DMA HALCYON_HEADER2 sequence.\n");
		return state_error;
	}
	buf++;
	cmd = (*buf++ & 0xFFFF0000) >> 16;

	switch(cmd) {
	case HC_ParaType_CmdVdata:

		/* 
		 * Command vertex data.
		 * It is assumed that the command regulator remains in this state
		 * until it encounters a double fire command or a header2 data.
		 * CHECK: Could vertex data accidently be header2 or fire?
		 * CHECK: What does the regulator do if it encounters a header1 
		 * cmd?
		 */

		while (buf < buf_end) {
			if (*buf == HALCYON_HEADER2) break;
			if ((*buf & HALCYON_FIREMASK) == HALCYON_FIRECMD) {
				buf++;
				if ((buf < buf_end) && 
				    ((*buf & HALCYON_FIREMASK) == HALCYON_FIRECMD))
					buf++;
				if ((buf < buf_end) && 
				    ((*buf & HALCYON_CMDBMASK) != HC_ACMD_HCmdB))
				    break;
			}
			buf++;
		}
		*buffer = buf;
		return state_command;

	case HC_ParaType_NotTex:
		hz_table = table1;
		break;
	case HC_ParaType_Tex:
	case (HC_ParaType_Tex | (HC_SubType_Tex1 << 8)):
		hz_table = table2;
		break;
	case (HC_ParaType_Tex | (HC_SubType_TexGeneral << 8)):
		hz_table = table3;
		break;
	default:
		DRM_ERROR("Invalid or unimplemented HALCYON_HEADER2 "
			  "DMA subcommand: 0x%x\n", cmd);
		*buffer = buf;
		return state_error;
	}

	while( buf < buf_end) {
		cmd = *buf++;
		if ((hz = hz_table[cmd >> 24])) {
			if (!(hz_mode = investigate_hazard(cmd, hz, &hc_sequence)))
				continue;
			if (hz_mode == 1) {
				buf--; 
				break;
			}
			return state_error; 
		}
	}

	if (hc_sequence.unfinished) {
		int ret;
		if ((ret = finish_current_sequence(&hc_sequence))) return ret;
	}

	*buffer = buf;
	return state_command;
}


static __inline__ verifier_state_t
via_check_header1( uint32_t const **buffer, const uint32_t *buf_end )
{
	uint32_t cmd;
	const uint32_t *buf = *buffer;
	verifier_state_t ret = state_command;

	while (buf < buf_end) {
		cmd = *buf;
		if ((cmd > ((0x3FF >> 2) | HALCYON_HEADER1)) &&
		    (cmd < ((0xC00 >> 2) | HALCYON_HEADER1))) {			
			if ((cmd & HALCYON_HEADER1MASK) != HALCYON_HEADER1 ) 
				break;
			DRM_ERROR("Invalid HALCYON_HEADER1 command. "
				  "Attempt to access 3D- or command burst area.\n");
			ret = state_error;
			break;
		} else if (cmd > ((0xCFF >> 2) | HALCYON_HEADER1)) {
			if ((cmd & HALCYON_HEADER1MASK) != HALCYON_HEADER1 ) 
				break;
			DRM_ERROR("Invalid HALCYON_HEADER1 command. "
				  "Attempt to access VGA registers.\n");
			ret = state_error;
			break;			
		} else {			
			buf += 2;
		}
	}
	*buffer = buf;
	return ret;
}



int via_verify_command_stream(const uint32_t * buf, unsigned int size)
{

	uint32_t cmd;
	const uint32_t *buf_end = buf + ( size >> 2 );
	verifier_state_t state = state_command;
	
	while (buf < buf_end) {
		switch (state) {
		case state_header2:
			state = via_check_header2( &buf, buf_end );
			break;
		case state_header1:
			state = via_check_header1( &buf, buf_end );
			break;
		case state_command:
			if (HALCYON_HEADER2 == (cmd = *buf)) 
				state = state_header2;
			else if ((cmd & HALCYON_HEADER1MASK) == HALCYON_HEADER1 ) 
				state = state_header1;
			else {
				DRM_ERROR("Invalid / Unimplemented DMA HEADER command. 0x%x\n",
					cmd);
				state = state_error;
			}
			break;
		case state_error:
		default:
			return DRM_ERR(EINVAL);			
		}
	}	
	return (state == state_error) ? DRM_ERR(EINVAL) : 0;
}

static void 
setup_hazard_table(hz_init_t init_table[], hazard_t table[], int size)
{
	int i;

	for(i=0; i<256; ++i) {
		table[i] = forbidden_command;
	}

	for(i=0; i<size; ++i) {
		table[init_table[i].code] = init_table[i].hz;
	}
}

void via_init_command_verifier( void )
{
	setup_hazard_table(init_table1, table1, sizeof(init_table1) / sizeof(hz_init_t));
	setup_hazard_table(init_table2, table2, sizeof(init_table2) / sizeof(hz_init_t));
	setup_hazard_table(init_table3, table3, sizeof(init_table3) / sizeof(hz_init_t));
}

Reply via email to